• 2021省选联考做题记录


    不管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\)

    然后考虑玄学优化:

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

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

    正确性显然,复杂度玄学,据称是 \(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);
    }
    
  • 相关阅读:
    大道至简第五章读后感
    课后作业1:字串加密
    String类中的equals()方法:
    构架之美读后感5
    构架之美读后感4
    构架之美读后感3
    构架之美读后感2
    构架之美读后感1
    关于联想y485p装win10显卡驱动问题
    软件需求与分析课堂讨论一
  • 原文地址:https://www.cnblogs.com/tale365/p/16093146.html
Copyright © 2020-2023  润新知