不管AB卷了就按洛谷的排列顺序来写吧。
1.卡牌游戏
问题可以看成是选最多 \(m\) 个 \(b_i\) 所能达到的最佳答案。
直接 \(a,b\) 混在一起排序(当然要记录下这个数原来是 \(a\) 还是 \(b\) )。然后要做的就是尽量砍掉两头的数。
直接双指针记录前面删 \(i\) 个数是后面最多删多少个数,扫一遍求个最小值就好了。
#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]\) 列出差分约束的不等式了。
#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\) 运行次数比较稳定)。
#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)\) 暴力统计答案就行。
#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\) ,传出谁,暴力跳就行了,当然正反都要处理一下下。
代码很无脑,调的时候出了一个小错误,基本算是一遍过:
#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)\) 。
#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)\) ,卡常。
#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\) 。
然后考虑玄学优化:
-
两个以上重复的数没有任何用处,去个重。
-
模数从大到小枚举,如果答案已经大于当前模数就可以停止枚举了。
正确性显然,复杂度玄学,据称是 \(O(nlognlogV)\) 的,可以尝试手玩一下。
#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);
}