【考试总结】2022-05-15

出题人

如果 ai0mod2 那么可以让 bi=ai2,剩下的 bj=ajbi

其实问题就是找到一个 {a} 的子集 S 使得存在 b 满足 b1+b2=S1,b2+b3=S2,b|S|+b1=S|S|

这时候 S 注意这里的等式一定是全集构成一个整环而不存在枝杈,否则让基环森林里面的任意一个环满足即可

继续观察上面的等式发现:由于 Si 全是奇数所以 |S| 是偶数,将 Si 按照下标奇偶性分成两组,那么两组的和均为 b

那么现在问题就是在 {a} 中找到两个权值和大小都相同的子集,通过枚举子集的方式可以规避三进制,使用哈希表存储信息即可

卡常技巧包括但不限于将使用哈希表的一侧大小设为 n22 或者当程序运行时间超过阀值输出 NO

Code Display
const int N=400;
int n,a[N],b[N],v[N];
pair<int,int> ans[N];
unordered_map<int,pair<int,int> > mp[N];
int sum1[1<<15],sum2[1<<17];
signed main(){
	freopen("problemsetter.in","r",stdin); freopen("problemsetter.out","w",stdout);
	n=read();
	rep(i,1,n) a[i]=read();
	rep(i,1,n) if(a[i]%2==0){
		puts("Yes");
		vector<int> b={a[i]/2};
		vector<pair<int,int> > ans;
		rep(j,1,n){
			if(i==j) ans.emplace_back(1,1);
			else{
				b.emplace_back(a[j]-b[0]);
				ans.emplace_back(1,b.size());
			}
		}
		rep(e,0,n-1) print(b[e]); putchar('\n');
		rep(e,0,n-1) print(ans[e].fir),print(ans[e].sec),putchar('\n');
		exit(0);
	}
	if(n<=2) puts("No"),exit(0);
	int n1=max(1ll,n/2-2),n2=n-n1;
	int U1=1<<n1,U2=1<<n2;
	rep(s,1,U1-1){
		int lb=s&(-s),id=__builtin_ctzll(lb)+1;
		sum1[s]=sum1[s^lb]+a[id];
	}
	rep(i,n1+1,n) b[i-n1]=a[i];
	rep(s,1,U2-1){
		int lb=s&(-s),id=__builtin_ctzll(lb)+1;
		sum2[s]=sum2[s^lb]+b[id];
	}
	auto output=[&](const pair<int,int> S,const pair<int,int> T){
		if(S.fir+S.sec+T.sec+T.fir==0) return ;
		vector<pair<int,int> >Plu,Min;
		for(int i=1;i<=n1;++i){
			if(S.fir>>(i-1)&1) Plu.emplace_back(a[i],i);
			if(S.sec>>(i-1)&1) Min.emplace_back(a[i],i);
		}
		for(int i=1;i<=n2;++i){
			if(T.fir>>(i-1)&1) Plu.emplace_back(b[i],i+n1);
			if(T.sec>>(i-1)&1) Min.emplace_back(b[i],i+n1);
		}
		sort(Plu.begin(),Plu.end());
		sort(Min.begin(),Min.end());
		int num=0,cur=0;
		b[++num]=cur;
		int siz=Plu.size();
		while(Min.size()||Plu.size()){
			if(Plu.size()==Min.size()){
				cur=Plu.back().fir-cur;
				ans[Plu.back().sec]={num,num+1};
				Plu.pop_back();
			}else{
				cur=Min.back().fir-cur;
				ans[Min.back().sec]={num,num==siz*2?1:num+1};
				Min.pop_back();
			}
			if(num<siz*2) b[++num]=cur;
		}
		for(int i=1;i<=n;++i) if(!ans[i].fir){
			ans[i]={1,++num};
			b[num]=a[i]-b[1];
		}
		puts("Yes");
		for(int i=1;i<=n;++i) print(b[i]); putchar('\n');
		for(int i=1;i<=n;++i) print(ans[i].fir),print(ans[i].sec),putchar('\n');
		exit(0);
	};

	rep(S,0,U1-1){
		for(int T=S;;T=(T-1)&S){
			int num=__builtin_popcount(T)-__builtin_popcount(S^T);
			mp[n+num][sum1[T]-sum1[S^T]]=make_pair(T,S^T);
			if(!T) break;
		}
	}
	rep(S,0,U2-1){
		for(int T=S;;T=(T-1)&S){
			int num=__builtin_popcount(T)-__builtin_popcount(S^T);
			if(mp[n-num].count(-sum2[T]+sum2[S^T])){
				output(mp[n-num][-sum2[T]+sum2[S^T]],make_pair(T,S^T));	
			}
			if(!T) break;

		}
		if(1.0*clock()/CLOCKS_PER_SEC>1.6) break;
	}
	puts("No");
	return 0;
}

彩色挂饰

建立圆方树,设 fx,c 表示 xx 表示点双的根的颜色为 c 时最少的联通块数

对于实点 x 可以直接合并下辖虚点的信息即 vfv,c

对于虚点 x 需要对联通分量里面的异色联通块数进行计算:

预处理出来点双里面每个子集的导出子图形成的联通块数量,以及每个子集填入哪种合法的颜色形成的联通块最少,再进行一个枚举子集再枚举子集填哪种颜色的 DP 来计算贡献

常见的建立圆方树的过程就能让父亲作为方点出边 vector 中的第一个点,要注意添加子集的过程中强制让包含 1 元素的子集填成所需颜色

注意这样的统计方式每个方点的父亲会被算两次,但是根这个实点只会被算一次,需要根据实际含义再加加减减

Code Display
const int N=2e5+10,inf=2e5+10;
int n,m,k,s;
vector<int> G[N],g[N];
int dp[N][25];
int col[N][64],con[N][64];
int dfn[N],low[N],stk[N],top,nds,tim;
int fa[N],ini[N];
unordered_set<int> edge[N];
bool exi[10][10];
inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline void tarjan(int x){
	dfn[x]=low[x]=++tim; stk[++top]=x;
	for(auto t:g[x]){
		if(!dfn[t]){
			tarjan(t); ckmin(low[x],low[t]);
			if(low[t]>=dfn[x]){
				++nds;
				G[nds].emplace_back(x);
				G[x].emplace_back(nds);
				do{
					G[nds].emplace_back(stk[top]);
					G[stk[top]].emplace_back(nds);
					--top;
				}while(stk[top+1]!=t);
				int siz=G[nds].size(),U=1<<siz;
				for(int i=0;i<siz;++i){
					for(int j=i+1;j<siz;++j){
						exi[i][j]=exi[j][i]=edge[G[nds][i]].find(G[nds][j])!=edge[G[nds][i]].end();
					}
				}
				for(int i=0;i<U;++i){
					for(int j=0;j<siz;++j) if(i>>j&1){
						int t=G[nds][j];
						fa[j]=j;
						if(ini[t]){
							if(!col[nds][i]) col[nds][i]=ini[t];
							else if(col[nds][i]!=ini[t]) col[nds][i]=-1;		
						}
					}
					for(int j=0;j<siz;++j) if(i>>j&1){
						for(int k=j+1;k<siz;++k) if(i>>k&1){
							if(exi[j][k]) fa[find(j)]=find(k);
						}
					}
					for(int j=0;j<siz;++j) if(i>>j&1) con[nds][i]+=fa[j]==j;
				}
			}
		}else ckmin(low[x],dfn[t]);
	}
	return ;
}
inline int solve(int x,int fat,int c){
	if(~dp[x][c]) return dp[x][c];
	if(x<=n){
		int sum=0;
		for(auto t:G[x]) if(t!=fat) sum+=solve(t,x,c);
		return dp[x][c]=sum;
	}
	if(ini[fat]&&ini[fat]!=c) return dp[x][c]=inf;	
	int num=G[x].size(),U=1<<num; --U;
	int dp1[64],dp2[64][25];
	rep(i,0,k) dp2[0][i]=0;
	for(int i=1;i<=U;++i){
		dp2[i][0]=inf;
		int lb=i&(-i),id=__builtin_ctzll(lb);
		for(int c=1;c<=k;++c){
			if(G[x][id]==fat) dp2[i][c]=dp2[i^lb][c];
			else dp2[i][c]=dp2[i^lb][c]+solve(G[x][id],x,c);
			ckmin(dp2[i][0],dp2[i][c]);
		}
	}
	dp1[0]=0;
	for(int S=1;S<=U;++S){
		dp1[S]=inf;
		for(int T=S;T;T=(T-1)&S){
			if(col[x][T]==-1) continue;
			int cur=col[x][T];
			if(T&1){
				if(cur!=c&&cur) continue;
				cur=c;
			}
			ckmin(dp1[S],dp1[S^T]+con[x][T]+dp2[T][cur]);
		}
	}
	return dp[x][c]=dp1[U];
}
signed main(){
	freopen("colorful.in","r",stdin); freopen("colorful.out","w",stdout);
	nds=n=read(); m=read(); k=read(); read();
	for(int i=1;i<=n;++i) ini[i]=read();
	for(int i=1;i<=m;++i){
		int u=read(),v=read();
		edge[u].insert(v);
		edge[v].insert(u);
		g[u].emplace_back(v);
		g[v].emplace_back(u);
	}	
	for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i);
	memset(dp,-1,sizeof(dp));
	int val=inf;
	if(ini[1]) val=solve(1,0,ini[1]);
	else rep(i,1,k) ckmin(val,solve(1,0,i));
	print(val-(nds-n)+1);
	return 0;
}

逆转函数

预处理每个 i 的上一次下一次的出现位置 prei,nxti,那么 [l,r] 存在逆转函数等价于 rl1 或者以下三个条件都满足:

  • [l+1,r1] 存在逆转函数

  • prerl 或者 al=al+rprer

  • nxtlr 或者 ar=al+rnxtr

注意到这种判定方式和回文串类似,而所求是每个 “类回文区间” 的 mmn(l,r) 的和,用 Manacher 来做,计算每个点作为回文中心时对答案的贡献

[l,r] 扩展成 [l1,r+1] 时维护 n(l,r) 以及新增的对答案的贡献是平凡的,但是在继承时存在两者取 min 的操作会让信息减少,那么先继承 2imid 的信息,再减至 mid+r[mid] 处,信息的减少和增加的处理方式一样

观察到如果保留最靠右的 i+riiiri 是不断右移的,而扩展本身会让最靠右的回文串的右端点右移,那么复杂度就是 Θ(n)

Code Display
const int N=6e5+10;
int a[N],pw[N],n,m;
int pre[N],lst[N],nxt[N];
int num[N],sum[N],r[N];
signed main(){
	freopen("invfunc.in","r",stdin); freopen("invfunc.out","w",stdout);
	n=read(); m=read();
	for(int i=1;i<=n;++i) a[i]=read();
	for(int i=n;i>=1;--i) a[i<<1]=a[i];
	rep(i,1,n*2+1) if(i&1) a[i]=m+1;
	pw[0]=1;
	for(int i=1;i<=n*2+1;++i){
		pw[i]=mul(pw[i-1],m);
		pre[i]=lst[a[i]];
		lst[a[i]]=i;
	}
	for(int i=1;i<=m+1;++i) lst[i]=n*2+2;
	for(int i=n*2+1;i>=1;--i){
		nxt[i]=lst[a[i]];
		lst[a[i]]=i;
	}
	auto check=[&](int l,int r){
		if(r-l<=1) return true;
		if(!l||r>2*n+1) return false;
		if(pre[r]>l&&a[l]!=a[l+r-pre[r]]) return false;
		if(nxt[l]<r&&a[r]!=a[l+r-nxt[l]]) return false;
		return true;
	};
	int ans=0,cen=num[1]=1;
	for(int i=2;i<=n*2;++i){
		if(i<=cen+r[cen]){
			r[i]=r[2*cen-i];
			sum[i]=sum[2*cen-i];
			num[i]=num[2*cen-i];
			while(i+r[i]>cen+r[cen]){
				if((i+r[i])&1) ckdel(sum[i],pw[m+1-num[i]]);
				int lef=cen*2-i-r[i],rig=cen*2-i+r[i];
				if(nxt[lef]>rig) --num[i];
				if(pre[rig]<lef+1) --num[i];
				--r[i];
			}
		}else num[i]=1;
		while(check(i-r[i]-1,i+r[i]+1)){
			++r[i];
			if(nxt[i-r[i]]>i+r[i]-1) ++num[i];
			if(pre[i+r[i]]<i-r[i]) ++num[i];
			if((i+r[i])&1) ckadd(sum[i],pw[m+1-num[i]]);
		}
		if(i+r[i]>=cen+r[cen]) cen=i;
		ckadd(ans,sum[i]);
	}
	print(ans);
	return 0;
}

posted @   没学完四大礼包不改名  阅读(75)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示