不管AB卷了就按洛谷的排列顺序来写吧。

1.卡牌游戏

问题可以看成是选最多 $m$ 个 $b_i$ 所能达到的最佳答案。

直接 $a,b$ 混在一起排序(当然要记录下这个数原来是 $a$ 还是 $b$ )。然后要做的就是尽量砍掉两头的数。

直接双指针记录前面删 $i$ 个数是后面最多删多少个数,扫一遍求个最小值就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include<cstdio>
#include<algorithm>
#define rep(a,b,c) for(int c=(a);c<=(b);++c)
#define drep(a,b,c) for(int c=(a);c>=(b);--c)
inline int read()
{
int res=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar();
while(ch<='9'&&ch>='0')res=res*10+(ch^48),ch=getchar();return res;
}
inline int Min(int x,int y){return x<y?x:y;} inline int Max(int x,int y){return x>y?x:y;}
const int N=1e6+10; struct Sort
{
int idx,val;bool flag;
inline bool operator<(const Sort &x)const{return val<x.val;}
}Q[N<<1];bool vis[N];
int main()
{
int n=read(),k=read();rep(1,n,i)Q[i].val=read(),Q[i].idx=i,Q[i].flag=1;
rep(1,n,i)Q[i+n].val=read(),Q[i+n].idx=i,Q[i+n].flag=0;std::sort(Q+1,Q+(n<<1|1));
int l=1,r=2*n;while(k>=0&&l<2*n)
{
if(vis[Q[l].idx])break;
vis[Q[l].idx]=1;k-=Q[l++].flag;
}
if(k<0)k+=Q[--l].flag,vis[Q[l].idx]=0;
int ans=Q[r].val-Q[l].val; while(l)
{
while(l<=r&&k>=0)
{
if(vis[Q[r].idx])break;
vis[Q[r].idx]=1;k-=Q[r--].flag;
}
if(k<0)k+=Q[++r].flag,vis[Q[r].idx]=0;
ans=Min(ans,Q[r].val-Q[l].val);
k+=Q[--l].flag;vis[Q[l].idx]=0;
}
printf("%d\n",ans);
}

2.矩阵游戏

首先容易构造出一个值域为 $\Z$ 的 $a’$ 数组(直接钦定第一排第一列为 $0$ 即可),然后考虑调整。

考虑对整行整列进行操作,发现给矩阵奇偶黑白染色后,对整排或整列的黑点 $+1$ ,白点 $-1$ 结果仍然合法。

然后设第 $i$ 行加了 $r_i$ ,第 $j$ 列加了 $c_j$ ,那么真实的 $a$ 满足:

$a_{i,j}=a’_{i,j}+(r_i-c_j)(i+j\bmod 2==1)$

$a_{i,j}=a’_{i,j}-(r_i-c_j)(i+j\bmod 2==0)$

然后就可以根据 $a_{i,j}\in[0,10^6]$ 列出差分约束的不等式了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<cstdio>
#include<cstring>
#define rep(a,b,c) for(int c=(a);c<=(b);++c)
typedef long long LL;
#define int LL
inline int read()
{
int res=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar();
while(ch<='9'&&ch>='0')res=res*10+(ch^48),ch=getchar();return res;
}
inline bool chkmin(int &x,const int &w){return w<x?x=w,true:false;}
inline void Swap(int &x,int &y){int tmp(x);x=y;y=tmp;}
const int N=310,M=610,W=1e6,INF=0x3f3f3f3f;
int a[N][N],b[N][N],G[M][M],dis[M],n,m;
inline bool BellmanFord()
{
memset(dis,0x3f,sizeof(dis));dis[0]=0;
int T,flag(true);for(T=0;T<=n+m&&flag;++T)
{
flag=false;rep(1,n+m,i)flag|=chkmin(dis[i],dis[0]+G[0][i]);
rep(1,n,i)rep(n+1,n+m,j)flag|=chkmin(dis[j],dis[i]+G[i][j]);
rep(n+1,n+m,i)rep(1,n,j)flag|=chkmin(dis[j],dis[i]+G[i][j]);
}
return T<n+m;
}
inline void Solve()
{
n=read();m=read();memset(G,0x3f,sizeof(G));
rep(2,n,i)rep(2,m,j)a[i][j]=read()-a[i-1][j-1]-a[i-1][j]-a[i][j-1];
rep(1,n+m,i)G[0][i]=0; rep(1,n,i)rep(1,m,j)
{
//-a[i][j]<=r[i]-c[j]<=W-a[i][j]
G[j+n][i]=W-a[i][j]; //r[i]-c[j]<=W-a[i][j]
G[i][j+n]=a[i][j]; //c[j]-r[i]<=a[i][j]
if(!((i^j)&1))Swap(G[j+n][i],G[i][j+n]);
}
if(!BellmanFord())return puts("NO"),void();puts("YES");
rep(1,n,i){rep(1,m,j)printf("%lld ",a[i][j]+(((i^j)&1)?1:-1)*(dis[i]-dis[j+n]));puts(""); }
}
signed main(){int T=read();while(T--)Solve();return 0;}

3.图函数

遇事不决先找性质,并且相信奇迹,$O(n^3)$ 过 $1000$ !

找一找性质,可以发现:一个点 $x$ 合法当且仅当它仅经过 $y\in(x,n)$ 的点能够同时存在 $i\rightarrow x,x\rightarrow i$ 的路径。

证:若 $u\in[0,x)$ 合法,$u$ 已经被删除;若不合法,不存在 $i\rightarrow u\rightarrow i$ ,则不存在 $i\rightarrow u\rightarrow v\rightarrow i$ 或 $i\rightarrow v\rightarrow u\rightarrow i$ 。

然后对于依次加边统计答案转化为第 $i$ 条边边权为 $i$ ,按一条路径经过边的最小值进行差分。

找最小值可以用 $\rm Floyd$ ,$O(n^3)$ 暴力也能过。(关于能不能过可以现场试一下,$\rm Floyd$ 运行次数比较稳定)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<cstdio>
#define rep(a,b,c) for(int c(a);c<=(b);++c)
#define drep(a,b,c) for(int c(a);c>=(b);--c)
inline int read()
{
int res=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar();
while(ch<='9'&&ch>='0')res=res*10+(ch^48),ch=getchar();return res;
}
template<typename T> inline T max(const T&x,const T&y){return x<y?y:x;}
template<typename T> inline T min(const T&x,const T&y){return x<y?x:y;}
template<typename T> inline void chkmax(T&x,const T&w){x<w?x=w:T();}
template<typename T> inline void chkmin(T&x,const T&w){w<x?x=w:T();}
const int N=1010,M=2e5+10;int u[M],v[M],G[N][N],n,m,S[M];
int main()
{
n=read();m=read();rep(1,m,i)u[i]=read(),v[i]=read(),G[u[i]][v[i]]=i;
drep(n,1,v)
{
rep(v+1,n,u)++S[min(G[v][u],G[u][v])];
rep(1,n,u)if(G[u][v])
{
int cur=G[u][v];
if(u>v)for(int k=1;k<v;++k)chkmax(G[u][k],min(cur,G[v][k]));
else rep(1,n,k)chkmax(G[u][k],min(cur,G[v][k]));
}
}
drep(m,1,i)S[i]+=S[i+1];rep(1,m+1,i)printf("%d ",n+S[i]);return 0;
}

4.数对

$B$ 卷的签到题我当然是不会的啦。

$a\leq5\times 10^5$ ,由于顺序无关,直接 $O(alna)$ 暴力统计答案就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<cstdio>
#define rep(a,b,c) for(int c=(a);c<=(b);++c)
inline int read()
{
int res=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar();
while(ch<='9'&&ch>='0')res=res*10+(ch^48),ch=getchar();return res;
}
const int N=5e5+10;int t[N],mx;
int main()
{
int n=read();long long ans=0;while(n--){int x=read();mx<x?mx=x:0;++t[x];}
rep(1,mx,i)if(t[i]){for(int j=i<<1;j<=mx;j+=i)ans+=1ll*t[i]*t[j];ans+=(t[i]-1)*t[i];}
printf("%lld\n",ans);return 0;
}

5.宝石

写了个贴着数据范围的复杂度 $O(n+c\sqrt{n}+q\sqrt{n}\ logn)$ 的垃圾做法。

首先考虑将 $P$ 序列下标映射到宝石上(因为 $P$ 不相同),然后就变成在这个路径上搞出最多连续的 $123…x$ 。

然后路径直接树剖搞出来映射到 $\rm dfs$ 序上,然后问题转化成当前选到了 $p$ ,经过一个区间后选到了谁。

分块常数小,我们要相信奇迹,直接上分块 $O(\sqrt{n}\ logn)$ 查询就行了(个人喜欢固定块长,复杂度较玄学)。

然后直接处理每个块内传入 $p$ ,传出谁,暴力跳就行了,当然正反都要处理一下下。

代码很无脑,调的时候出了一个小错误,基本算是一遍过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include<cstdio>
#define rep(a,b,c) for(int c(a);c<=(b);++c)
#define drep(a,b,c) for(int c(a);c>=(b);--c)
#define grep(b,c) for(int c=head[(b)];c;c=nxt[c])
inline int read()
{
int res=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar();
while(ch<='9'&&ch>='0')res=res*10+(ch^48),ch=getchar();return res;
}
template<typename T> inline T min(const T&x,const T&y){return x<y?x:y;}
template<typename T> inline T max(const T&x,const T&y){return x<y?x:y;}
template<typename T> inline void chkmin(T&x,const T&w){w<x?x=w:T();}
template<typename T> inline void chkmax(T&x,const T&w){x<w?x=w:T();}
template<typename T> inline void Swap(T&x,T&y){T tmp=x;x=y;y=tmp;}
const int N=2e5+10,BK=512,C=5e4+10;int head[N],des[N<<1],nxt[N<<1],cgt,loc[N],n,m,c,w[N];
inline void ins(const int &x,const int &y){nxt[++cgt]=head[x];des[head[x]=cgt]=y;nxt[++cgt]=head[y];des[head[y]=cgt]=x;}
namespace Block //nxt --> 传入将要打入的值,返回经过后将要打入的值 ,inv --> 反转区间后的nxt
{
int nxt[N/BK+10][C],inv[N/BK+10][C],loc[N],ql[N/BK+10],qr[N/BK+10],Blk,vis[C],a[N];
inline void Init()
{
Blk=n/BK;rep(1,Blk,i){ql[i]=qr[i-1]+1;qr[i]=i*BK;rep(ql[i],qr[i],j)loc[j]=i;}
if(n%BK!=0){ql[Blk+1]=qr[Blk]+1;qr[++Blk]=n;rep(ql[Blk],qr[Blk],j)loc[j]=Blk;}
rep(1,Blk,B)
{
rep(0,c+1,i)nxt[B][i]=inv[B][i]=i;
rep(ql[B],qr[B],i)inv[B][a[i]]=inv[B][a[i]+1];
drep(qr[B],ql[B],i)nxt[B][a[i]]=nxt[B][a[i]+1];
}
}
inline void qry(const int &l,const int &r,int &P,const bool &Inv=false)
{
if(Inv)
{
const int &Bl=loc[l],&Br=loc[r];
if(Bl==Br){drep(r,l,i)P+=(a[i]==P);return;}
drep(r,ql[Br],i)P+=(a[i]==P);
for(int B=Br-1;B>Bl;--B)P=inv[B][P];
drep(qr[Bl],l,i)P+=(a[i]==P);
}
else
{
const int &Bl=loc[l],&Br=loc[r];
if(Bl==Br){ rep(l,r,i)P+=(a[i]==P);return;}
rep(l,qr[Bl],i)P+=(a[i]==P);
for(int B=Bl+1;B<Br;++B)P=nxt[B][P];
rep(ql[Br],r,i)P+=(a[i]==P);
}
}
}
namespace TreeCut
{
using Block::qry;using Block::a;int mxs[N],dep[N],siz[N],top[N],fa[N],idx[N],cdt;
struct Pair{int x,y;inline Pair(const int &X=0,const int &Y=0){x=X;y=Y;}}stk[55];int Top;
inline void dfs1(const int &u=1,const int &f=0)
{
dep[u]=dep[fa[u]=f]+1;siz[u]=1;int mx=0;
grep(u,i)if(des[i]!=f){int v=des[i];dfs1(v,u);siz[u]+=siz[v];mx<siz[v]?mx=siz[mxs[u]=v]:0;}
}
inline void dfs2(const int &u=1,const int &fa=0,const int &tp=1)
{
top[u]=tp;a[idx[u]=++cdt]=w[u];if(mxs[u])dfs2(mxs[u],u,tp);
grep(u,i)if(des[i]!=mxs[u]&&des[i]!=fa)dfs2(des[i],u,des[i]);
}
inline int Qry(int v,int u)
{
int P=1;while(top[u]!=top[v])
{
if(dep[top[v]]<dep[top[u]])
{
qry(idx[top[u]],idx[u],P,true);
u=fa[top[u]];
}
else
{
stk[++Top]=Pair(idx[top[v]],idx[v]);
v=fa[top[v]];
}
}
if(dep[u]<dep[v])qry(idx[u],idx[v],P);
else qry(idx[v],idx[u],P,true);
while(Top)qry(stk[Top].x,stk[Top].y,P),--Top;
return P-1;
}
}
using TreeCut::Qry;
int main()
{
n=read();m=read();c=read();rep(1,c,i)loc[read()]=i;
rep(1,n,i)w[i]=loc[read()];rep(2,n,i)ins(read(),read());
TreeCut::dfs1();TreeCut::dfs2();Block::Init();int Q=read();
while(Q--)printf("%d\n",Qry(read(),read()));return 0;
}

6.滚榜

首先尝试相信奇迹打一个 $O(n!\times n)$ 的暴力,然后仍然不会 。可以发现 $b$ 序列有贪心分法。

不妨令 $f_i=a_i+b_i$ ,考虑枚举排列 $P$,先报 $P_1$ ,然后必须使 $f_{P_1}>\max {a}$ ,于是 $b_1$ 的值就出来了。

然后接下来就继续贴着合法条件给出 $b$ ,最后剩余的一股脑塞给 $P_n$ 就行。

考虑把上面式子化一化,发现 $Sum=\sum (a_{P_{i-1}}-a_{P_i}+(P_i>P_{i-1}))\times(n-i+1)$ ,发现 $a_i$ 的贡献可以拆开来算。

然后 $dp_{S,i,j}$ 表示选了 $S$ 集合,最后为 $i$ ,和为 $j$ 的方案数 $\rm dp$ 就行。复杂度 $O(2^nn^2m)$ 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<cstdio>
#define rep(a,b,c) for(int c(a);c<=(b);++c)
#define drep(a,b,c) for(int c(a);c>=(b);--c)
typedef long long LL;
inline int read()
{
int res=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar();
while(ch<='9'&&ch>='0')res=res*10+(ch^48),ch=getchar();return res;
}
const int N=15,M=510; int n,m,a[N],mx,idx;LL dp[1<<13|15][N][M];int cnt[1<<13|15];
int main()
{
n=read();m=read();for(int i=0;i<n;++i) a[i]=read(),mx<a[i]?mx=a[idx=i]:0;
for(int i=0;i<n;++i)
{
int tmp=(mx-a[i]+(i>idx))*n;
if(tmp<=m)dp[1<<i][i][tmp]=1;cnt[1<<i]=1;
}
for(int S=0;S<(1<<n);++S)if(!cnt[S])
{
cnt[S]=cnt[S^(S&-S)]+1;
for(int i=0;i<n;++i)if((S>>i)&1)
{
int T=S^(1<<i);
for(int k=0;k<n;++k)if((T>>k)&1)
{
int dlt=(a[k]-a[i]+(i>k))*(n-cnt[S]+1);dlt<0?dlt=0:0;
rep(dlt,m,j)dp[S][i][j]+=dp[T][k][j-dlt];
}
}
}
LL ans=0;for(int i=0;i<n;++i)rep(0,m,j)ans+=dp[(1<<n)-1][i][j];
printf("%lld\n",ans);return 0;
}

7.支配

不会支配树,不做了。 于是抄了抄题解 (逃

以 $1$ 为一个有向图的 “根” ,然后暴力枚举每个点找到这个点的支配集(就是直接 $dfs$ 看哪些点不再可达)。

然后由一个点向距离它最近的支配点连边建树(可以用 $\rm topo$)。

如果一个点 $x$ 的 $fa_x$ 不再支配 $x$ ,那么 $x$ 整棵子树会改变并计入答案。

考虑当" $1$ 不经过 $fa_x$ 可以到达 $u$ “且” $v$ 不经过 $fa_x$ 可以到达 $x$ " ,$fa_x$ 不再支配 $x$ 。

第一个命题可以转化为 $fa_x$ 不支配 $v$ ,第二个命题可以建反图预处理。然后特判 $fa_x =v$ 和 $fa_x=1$ 的情况不成立。

最后处理出满足条件的点从上到下遍历支配树统计答案即可。复杂度 $O((n+q)n)$ ,卡常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include<cstdio>
#include<vector>
#include<cstring>
#define rep(a,b,c) for(int c(a);c<=(b);++c)
#define drep(a,b,c) for(int c(a);c>=(b);--c)
#define pb push_back
inline int read()
{
int res=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar();
while(ch<='9'&&ch>='0')res=res*10+(ch^48),ch=getchar();return res;
}
const int N=3010,M=20010;std::vector<int> G[N],I[N],T[N];
bool w[N][N],vis[N],W[N][N];int n,m,deg[N],fa[N],stk[N],top,siz[N],u,v,ans;
//w[i][j]-> i支配j ,W[i][j]-> i不经过fa[i]是否可以到达j
int Rt;inline void dfs0(const int &u){if(u==Rt)return;vis[u]=true;for(int v:G[u])if(!vis[v])dfs0(v);}
inline void dfs1(const int &u){siz[u]=1;for(int v: T[u]) dfs1(v),siz[u]+=siz[v];}
inline void dfs2(const int &u){if(u==fa[Rt])return;W[Rt][u]=vis[u]=true;for(int v:I[u])if(!vis[v])dfs2(v);}
inline void dfs3(const int &u){if(vis[u]){ans+=siz[u];return;}for(int v: T[u])dfs3(v);}
inline void topo()
{
stk[top=1]=1;while(top)
{
int u=stk[top--];//printf("topo %d\n",u);
rep(1,n,v)if(w[u][v]&&!(--deg[v]))T[u].push_back(stk[++top]=v),fa[v]=u;
}
}
int main()
{
n=read();m=read();int Q=read();
rep(1,m,i){int u=read(),v=read();G[u].push_back(v);I[v].push_back(u);}
for(Rt=1;Rt<=n;++Rt)
{
memset(vis,0,(n+1));dfs0(1);vis[Rt]=true;
rep(1,n,i)if(!vis[i])w[Rt][i]=true,++deg[i];
}
topo();dfs1(1);for(Rt=1;Rt<=n;++Rt)memset(vis,0,n+1),dfs2(Rt);
rep(1,n,i)w[i][i]=true;
while(Q--)
{
u=read();v=read();memset(vis,0,n+1);
rep(2,n,i)if(fa[i]!=v&&fa[i]!=1&&!w[fa[i]][u]&&W[i][v])vis[i]=true;//,printf("QwQ %d\n",i);
ans=0;dfs3(1);printf("%d\n",ans);
}

}

8.取模

玄学题,(其实我不会证复杂度也不想证了QAQ (~懒

考虑 $O(n^2logn)$ 做法,可以枚举模数,对剩下的数按取模后从大到小排序。不妨设为 $b$ 。

最大与次大值加起来有可能是答案,然后继续用双指针枚举 $a_l+a_r<a_i$ 的 $l,r$ 取 $\max$ 。

然后考虑玄学优化:

  1. 两个以上重复的数没有任何用处,去个重。

  2. 模数从大到小枚举,如果答案已经大于当前模数就可以停止枚举了。

正确性显然,复杂度玄学,据称是 $O(nlognlogV)$ 的,可以尝试手玩一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<cstdio>
#include<cstring>
#include<algorithm>
#define rep(a,b,c) for(int c(a);c<=(b);++c)
#define drep(a,b,c) for(int c(a);c>=(b);--c)
inline int read()
{
int res=0;char ch=getchar();while(ch<'0'||ch>'9')ch=getchar();
while(ch<='9'&&ch>='0')res=res*10+(ch^48),ch=getchar();return res;
}
inline void chkmax(int &x,const int &w){x<w?x=w:0;}
inline bool cmp(const int &x,const int &y){return x>y;}
const int N=2e5+10;int aa[N],n,b[N],ans,c[N],a[N];
int main()
{
int nn=read();rep(1,nn,i)aa[i]=read();std::sort(aa+1,aa+nn+1,cmp);
n=2;a[1]=aa[1];a[2]=aa[2];rep(3,nn,i)if(aa[i]!=aa[i-2])a[++n]=aa[i];
for(int i=1;i<=n&&ans<a[i];++i)
{
int B=0,C=0;rep(1,n,j)if(i!=j){int tmp=a[j]%a[i];if(tmp<a[i]/2)c[++C]=tmp;else b[++B]=tmp;}
std::sort(b+1,b+B+1,cmp);std::sort(c+1,c+C+1,cmp);
if(B>=2)chkmax(ans,b[1]+b[2]-a[i]);if(C>=2)chkmax(ans,c[1]+c[2]);
int p=1;drep(B,1,j)
{
while(p<=C&&c[p]+b[j]>=a[i])++p;
if(p==C+1)break;chkmax(ans,c[p]+b[j]);
}
}
printf("%d\n",ans);
}