• atcoder 做题记录


    AGC043

    B 123 Triangle

    题意

    给定长为 \(n\) 的序列 \(a\)。有递推关系 \(f_{k,x}=|f_{k-1,x}-f_{k-1,x+1}|(k>1,x\le n-k+1),f_{1,x}=a_x\)。求 \(f_{n,1}\)

    数据范围\(n\le 10^6,1\le a_i\le 3\)

    view solution

    solution

    先把 \(n=1\) 判掉,然后 \(a_i\in[0,2]\)。注意到 \(1\) 的特殊性:如果原序列存在 \(1\),那么最后答案一定是 \(0,1​\) 中的一个。

    对于 \(f_i\),如果存在一个 \(1\) 其左边/右边不是 \(1\),那么在 \(f_{i+1}\) 中一定存在至少一个 \(1\)。否则所有数都是 \(1\)\(f_{i+1}\) 中都是 \(0\)

    所以如果有 \(1\) 的话,只需要判断答案的奇偶性。注意到 \(|a-b|\bmod 2=a+b \bmod 2\)\(a_i\)\(f_{n,0}\) 的贡献是 \(\binom{n-1}{i-1}\)。所以答案是

    \[\sum_{i=1}^n a_i\binom{n-1}{i-1}\bmod 2 \]

    使用 Lucas 定理即可。

    对于没有 \(1\) 的情况,把 \(2\) 视为 \(1\) 再跑即可。

    view code
    #include <bits/stdc++.h>
    using namespace std;
    const int N=1e6+5;
    inline int binom(int n,int m){return (n&m)==m?1:0;}
    int n,a[N];
    char s[N];
    int main(){
    	scanf("%d",&n);
    	scanf("%s",s+1);
    	for(int i=1;i<=n;++i)a[i]=s[i]-'0';
    	if(n==1){
    		printf("%d\n",a[1]);
    		return 0;
    	}
    	for(int i=1;i<n;++i)a[i]=abs(a[i]-a[i+1]);
    	--n;
    	bool flag=0;
    	for(int i=1;i<=n;++i){
    		if(a[i]==1)flag=1;
    	}
    	int key=flag?1:2;
    	int sum=0;
    	for(int i=1;i<=n;++i)
    		if(a[i]==key)sum^=binom(n-1,i-1);
    	printf("%d\n",sum?key:0);
    	return 0;
    }
    

    C Giant Graph

    题意

    给定三个简单无向图 \(G_1,G_2,G_3\),点数均为 \(n\)

    另根据这三张图构造一个有 \(n^3\) 个点的图 \(G\),图 \(G\)

    • \(\forall (u,v)\in G_1,a,b\in[1,n]\),连边 \((u,a,b),(v,a,b)\)
    • \(\forall (u,v)\in G_2,a,b\in[1,n]\),连边 \((a,u,b),(a,b,v)\)
    • \(\forall (u,v)\in G_3,a,b\in[1,n]\),连边 \((a,b,u),(a,b,v)\)

    对于 \(G\) 中的任意一个点 \((x,y,z)\),定义其点权为 \(10^{18(x+y+z)}\)

    \(G\) 的最大权独立集的大小模 \(998244353\) 的值。

    数据范围\(n,m\le 10^5\)

    view solution

    solution

    把原问题转博弈题。

    首先因为一个点的权值很大,所以我们肯定优先选 \(x+y+z\) 大的,这样与 \((x,y,z)\) 有边相连的点肯定不能选。

    我们把要选的点视为必败态,这样所有能一步走到必败态的点就是必胜态。答案是所有必胜态的点的权值之和。

    把每张图边定向,从小的连向大的,这样变成一个博弈的 DAG,并求出所有点的 SG 函数。如果 \((x,y,z)\) 满足 \(sg_1(x)\oplus sg_2(y)\oplus sg_3(z)=0\),那么点 \((x,y,z)\) 是必败态,即我们需要选的点。注意到 \(sg\) 值是 \(O(\sqrt{n})\) 级别的,预处理每张图中,\(sg(a)=i\) 的所有 \(a\)\(10^{18a}\) 之和,然后枚举 \(sg_1(x)\)\(sg_2(y)\) 即可。复杂度 \(O(n+m)\)

    view code
    #include <bits/stdc++.h>
    using namespace std;
    inline int read(){
    	int s=0,f=1;char ch=getchar();
    	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
    	return s*f;
    }
    const int N=1e5+5,mod=998244353;
    inline int quick_pow(int a,int b){
    	int ret=1;
    	for(;b;b>>=1,a=1ll*a*a%mod)if(b&1)ret=1ll*ret*a%mod;
    	return ret;
    }
    const int pw=quick_pow(10,18);
    int n;
    struct Graph{
    	vector<int> e[N];
    	int cnt[N],sg[N],m,mx;
    	bool vis[N];
    	void dfs(int u){
    		if(sg[u]!=-1)return;
    		for(int v:e[u])
    			dfs(v);
    		for(int v:e[u])
    			vis[sg[v]]=1;
    		for(int i=0;;++i)if(!vis[i]){
    			sg[u]=i;
    			break;
    		}
    		for(int v:e[u])
    			vis[sg[v]]=0;
    	}
    	void work(){
    		m=read();
    		for(int i=1,u,v;i<=m;++i){
    			u=read();v=read();
    			if(u>v)swap(u,v);
    			e[u].push_back(v);
    		}
    		memset(sg+1,-1,n<<2);
    		for(int i=1;i<=n;++i){
    			dfs(i);cnt[sg[i]]=(cnt[sg[i]]+quick_pow(pw,i))%mod;
    			mx=max(mx,sg[i]);
    		}
    	}
    }G1,G2,G3;
    int main(){
    	n=read();
    	G1.work();
    	G2.work();
    	G3.work();
    	int ans=0;
    	for(int i=0;i<=G1.mx;++i)if(G1.cnt[i])
    		for(int j=0;j<=G2.mx;++j)if(G2.cnt[j])
    			if(G3.cnt[i^j])ans=(ans+1ll*G1.cnt[i]*G2.cnt[j]%mod*G3.cnt[i^j])%mod;
    	printf("%d\n",ans);
    	return 0;
    }
    

    D Merge Triplets

    题意

    \(n\) 个大小为 \(3\) 的序列,总共 \(3n\) 个元素,这 \(3n\) 个元素构成一个 \(1\sim 3n\) 的排列。

    另有一个排列 \(P\),每次选出所有非空序列中的第一个元素中最小的一个,把这个元素放到 \(P\) 的末尾,并把它从其所在序列中删除。一直操作直到所有序列为空,即 \(P\) 变成一个 \(1\sim 3n\) 的排列。

    对于所有生成 \(n\) 个序列的方式,求能生成多少个不同的排列 \(P\),答案取模。

    数据范围\(n\le 2000\)

    view solution ### solution

    对于一个大小为 \(3\) 的序列,它在 \(P\) 中的位置一定是下面三种情况之一:

    • 一个长为为 \(3\) 的连续段
    • 两个连续段,长度为 \(1+2/2+1\)
    • 三个连续段,长度为 \(1,1,1\)

    也就是,只要一个排列 \(P\) 满足以下限制,它一定能被构造出来:

    能被前缀最大值分为若干段,每段的长度 \(\le 3\),且 \(2\) 的数量 \(\le 1\) 的数量。

    DP,\(f_{i,j}\) 表示考虑完前 \(i\) 个数,\(1\) 的数量 \(-2\) 的数量 \(=j\) 的方案数。

    复杂度 \(O(n^2)\)

    view code
    #include <bits/stdc++.h>
    using namespace std;
    const int N=2005;
    int C[N*3][3],mod;
    inline int add(int a,int b){return (a+b>=mod)?a+b-mod:a+b;}
    inline void init(int n){
    	C[0][0]=1;
    	for(int i=1;i<=n;++i){
    		C[i][0]=1;
    		for(int j=1;j<3;++j)C[i][j]=add(C[i-1][j-1],C[i-1][j]);
    	}
    }
    struct Val{
    	int f[6*N];
    	inline int& operator[](int x){return f[x+3*N];}
    };
    int n;
    Val f[N*3];
    int main(){
    	cin>>n>>mod;
    	init(n*3);
    	f[0][0]=1;
    	for(int i=1;i<=3*n;++i){
    		for(int j=1;j<=3&&j<=i;++j){
    			int v=C[i-1][j-1];
    			if(j==3)v=2ll*v%mod;
    			for(int k=-i+1;k<i;++k){
    				if(j==1)f[i][k+1]=(f[i][k+1]+1ll*v*f[i-j][k])%mod;
    				else if(j==2)f[i][k-1]=(f[i][k-1]+1ll*v*f[i-j][k])%mod;
    				else f[i][k]=(f[i][k]+1ll*v*f[i-j][k])%mod;
    			}
    		}
    	}
    	int ans=0;
    	for(int i=0;i<=3*n;++i)ans=add(ans,f[3*n][i]);
    	printf("%d\n",ans);
    	return 0;
    }
    

    E Topology

    题意

    平面上有 \(n\) 个点,分别位于 \((i+\frac{1}{2},\frac{1}{2})(i\in[0,n-1])\),以及一个封闭曲线 \(C\)。给定 \(2^n\) 个状态,每个状态 \(f_S\) 表示在考虑 \(S\) 集合内的所有点时,曲线 \(C\) 能否在不接触 \(S\) 集合内的点,移动到所有点的纵坐标都 \(<0\) 的位置。

    请你根据 \(f\) 构造一个满足条件的 \(C\),或者判定无解。

    数据范围\(n\le 8\)

    view solution

    首先容易发现,\(f\) 具有传递性,即如果 \(f_{S}=1\),那么对于 \(T\subseteq S,f_{T}=1\);如果 \(f_{S}=0\),那么对于 \(S\subseteq T,f_{T}=0\)。如果违反了传递性显然无解。

    我们现在找到所有的 \(S\),满足所有 \(S\) 的子集都能解出来,\(S\)\(S\) 的所有超集都不能被解出来。如果我们的构造满足:对于 \(S\) 无法解出,而删掉任何一个点都能解出,那么我们把所有 \(S\) 的构造连到同一个点上把它们拼起来,就满足了题面的所有限制。

    构造之前,先考虑 spj 怎么写:从封闭曲线的一个点出发走一圈,如果经过点 \(i\) 的上方,往序列里写下一个 \(u_i\);如果经过点 \(i\) 的下方,写下一个 \(t_i\)。如果出现了连续的 \(u_i,u_i\) 或者 \(t_i,t_i\),那么这两步可以删掉,不会影响 \(i\) 是否被 \(C\) 包住,也就不会影响 \(C\) 是否能解出。如果一直把整个序列都删完了,那么不存在任何一个点被包住,即 \(C\) 可以解出,否则 \(C\) 无法解出。

    会了判定之后递归构造方案(方案要满足:对于 \(S\) 无法解出,而删掉任何一个点都能解出):

    • 如果当前集合内只有 \(1\) 个点,把这个点包一圈即可
    • 先把最靠左的 \(i\) 去掉,设 \(T\) 表示 \(S'=S\setminus \{i\}\) 的方案,那么我们构造 \(u_iTu_it_iT't_i\)\(T'\) 表示把 \(T\) 中的方案倒序之后的方案。容易发现,如果任何一个点被删掉(删掉它的所有 \(u_i,t_i\)),那么整个序列就能被删完;否则整个序列不能被删掉任何一个元素。
    view code
    #include <bits/stdc++.h>
    using namespace std;
    const int N=10;
    #define pr pair<int,int>
    #define mp make_pair
    #define fi first
    #define se second
    #define pb push_back
    #define ins(x,y) ret.pb(mp(x,y))
    inline vector<pr> solve(int s,int pre){
    	int x=__builtin_ctz(s);
    	vector<pr> ret;
    	for(int i=pre+1;i<=x;++i)
    		ins(i,0);
    	if(__builtin_popcount(s)==1){
    		ins(x+1,0);ins(x+1,1);
    		ins(x,1);ins(x,0);
    		for(int i=x;i>pre;--i)
    			ins(i-1,0);
    		return ret;
    	}
    	ins(x+1,0);
    	vector<pr> T=solve(s^(1<<x),x+1);
    	for(pr p:T)ret.pb(p);
    	ins(x,0);
    	ins(x,1);
    	ins(x+1,1);
    	ins(x+1,0);
    	T.pop_back();
    	reverse(T.begin(),T.end());
    	for(pr p:T)ret.pb(p);
    	ins(x+1,0);
    	ins(x+1,1);
    	ins(x,1);
    	ins(x,0);
    	for(int i=x-1;i>=pre;--i)ins(i,0);
    	return ret;
    }
    int n;
    char s[1<<N];
    int a[N],f[1<<N];
    vector<pr> ans;
    int main(){
    	scanf("%d",&n);
    	scanf("%s",s);
    	int tot=(1<<n);
    	for(int i=0;i<tot;++i){
    		f[i]=s[i]-'0';
    		for(int j=0;j<n;++j)
    			if(i&(1<<j)){
    				if(f[i]==1&&f[i^(1<<j)]==0){
    					puts("Impossible");
    					return 0;
    				}
    			}
    	}
    	ans.pb(mp(0,0));
    	for(int i=1;i<tot;++i){
    		if(!f[i]){
    			bool flag=1;
    			for(int j=0;j<n;++j)if(i&(1<<j)){
    				if(!f[i^(1<<j)])flag=0;
    			}
    			if(flag){
    				vector<pr> cur=solve(i,0);
    				for(pr p:cur)ans.pb(p);
    			}
    		}
    	}
    	puts("Possible");
    	printf("%d\n",(int)ans.size()-1);
    	for(pr p:ans)printf("%d %d\n",p.fi,p.se);
    	return 0;
    }
    

    F Jewelry Box

    题意

    \(N\) 个珠宝商店。

    每个商店卖 \(K_i\) 珠宝,第 \(i\) 个商店的第 \(j(1\le j\le K_i)\) 珠宝拥有三个独立的属性 \((S,P,C)\) 依次表示重量,价格,数量。

    现在有 \(Q\) 组询问,每次给定一个 \(A_i\),询问能否够构造 \(A_i\) 个“珠宝盒”,如果可以则输出最小的花费(即购买的珠宝的价格之和)否则输出 \(-1\)

    一个“珠宝盒”是一个包含 \(N\) 个珠宝的盒子,且满足如下条件:

    • 盒子内部的第 \(i\) 个珠宝从第 \(i\) 个珠宝商店处购买。
    • 满足 \(M\) 条约束:
      • 对于第 \(i\) 条约束:此盒子内第 \(V_i\) 珠宝的重量应当不超过\(U_i\) 个珠宝的重量 \(+ W_i\)

    数据范围

    \(N,K_i\le 30,S_{i,j}\le 10^9,P_{i,j}\le 30,C_{i,j}\le 10^{12},M\le 50,Q\le 10^5,A_i\le 3\times 10^{13},W_i\le 10^9\)

    view soluiton

    参见 sxTQX 的博客 线性规划对偶问题

    view code
    #include <bits/stdc++.h>
    using namespace std;
    #define int long long
    inline int read(){
    	int s=0,f=1;char ch=getchar();
    	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
    	return s*f;
    }
    namespace Flow{
    	const int M=1e5+5,N=1e5+5,inf=1e17;
    	struct Edge{int to,next,flow,cost;}e[M];
    	int dis[N],head[N],cur[N],tot,ecnt=1,h[N],p[N];
    	inline void adde(int u,int v,int flow,int cost){
    		e[++ecnt]=(Edge){v,head[u],flow,cost};head[u]=ecnt;
    		e[++ecnt]=(Edge){u,head[v],0,-cost};head[v]=ecnt;
    	}
    	bool inq[N];
    	bool SPFA(int s,int t){
    		memset(dis+1,0x3f,tot<<3);
    		dis[s]=0;
    		queue<int> q;
    		q.push(s);
    		while(!q.empty()){
    			int u=q.front();q.pop();
    			inq[u]=0;
    			cur[u]=head[u];
    			for(int i=head[u],v;i;i=e[i].next){
    				if(!e[i].flow)continue;
    				v=e[i].to;
    				if(dis[v]>dis[u]+e[i].cost){
    					dis[v]=dis[u]+e[i].cost;
    					if(!inq[v])q.push(v),inq[v]=1;
    				}
    			}
    		}
    		memcpy(p+1,dis+1,tot<<3);
    		return dis[t]<=inf;
    	}
    	bool dijkstra(int s,int t){
    		for(int i=1;i<=tot;++i)h[i]+=p[i];
    		priority_queue<pair<int,int> > q;
    		q.push(make_pair(0,s));
    		memset(dis+1,0x3f,tot<<3);
    		memset(inq+1,0,tot);
    		dis[s]=0;
    		while(!q.empty()){
    			int u=q.top().second;q.pop();
    			if(inq[u])continue;
    			inq[u]=1;
    			cur[u]=head[u];
    			for(int i=head[u],v;i;i=e[i].next){
    				if(!e[i].flow)continue;
    				v=e[i].to;
    				if(dis[v]>dis[u]+e[i].cost-h[v]+h[u]){
    					dis[v]=dis[u]+e[i].cost-h[v]+h[u];
    					q.push(make_pair(-dis[v],v));
    				}
    			}
    		}
    		memcpy(p+1,dis+1,tot<<3);
    		return dis[t]<=inf;
    		
    	}
    	bool vis[N];
    	int dinic(int u,int t,int flow){
    		if(u==t)
    			return flow;
    		vis[u]=1;
    		int ret=0,f;
    		for(int&i=cur[u];i;i=e[i].next){
    			int v=e[i].to;
    			if(!e[i].flow||dis[v]!=dis[u]+e[i].cost-h[v]+h[u]||vis[v])continue;
    			f=dinic(v,t,min(flow,e[i].flow));
    			e[i].flow-=f;e[i^1].flow+=f;
    			ret+=f;flow-=f;
    		}
    		vis[u]=0;
    		if(f)dis[u]=inf;
    		return ret;
    	}
    }
    const int N=55,inf=1e17;
    int s[N][N],p[N][N],c[N][N],s1[N],p1[N],c1[N],k[N],n,id[N][N],tot;
    int cnt,flow[N*N],cost[N*N],F[N*N],m,per[N];
    inline bool cmp(int x,int y){
    	return s1[x]<s1[y];
    }
    signed main(){
    	n=read();
    	int S=++tot,T=++tot;
    	for(int i=1;i<=n;++i){
    		k[i]=read();
    		for(int j=1;j<=k[i];++j){
    			s1[j]=read();
    			p1[j]=read();
    			c1[j]=read();
    			if(j>1)id[i][j]=++tot;
    			else id[i][j]=S;
    			per[j]=j;
    		}
    		sort(per+1,per+1+k[i],cmp);
    		for(int j=1;j<=k[i];++j){
    			s[i][j]=s1[per[j]];
    			p[i][j]=p1[per[j]];
    			c[i][j]=c1[per[j]];
    		}
    		id[i][k[i]+1]=++tot;
    		for(int j=2;j<=k[i]+1;++j){
    			Flow::adde(id[i][j-1],id[i][j],p[i][j-1],0);
    			Flow::adde(id[i][j-1],id[i][j],inf,c[i][j-1]);
    			Flow::adde(id[i][j],id[i][j-1],inf,0);
    		}
    		Flow::adde(id[i][k[i]+1],T,inf,0);
    	}
    	Flow::tot=tot;
    	m=read();
    	while(m--){
    		int u,v,w;
    		u=read();v=read();w=read();
    		int flag=1;
    		for(int i=1;i<=k[v];++i){
    			while(flag<=k[u]&&s[u][flag]+w<s[v][i])++flag;
    			Flow::adde(id[v][i],id[u][flag],inf,0);
    		}
    	}
    	int mf=0,mc=0;
    	for(Flow::SPFA(S,T);Flow::dijkstra(S,T);){
    		int f=Flow::dinic(S,T,inf);
    		flow[++cnt]=mf;
    		cost[cnt]=mc;
    		int d=Flow::dis[T]+Flow::h[T]-Flow::h[S];
    		F[cnt]=d;
    		if((__int128)f*d>=inf)break;
    		mf+=f;
    		mc+=f*d;
    	}
    	int q=read();
    	while(q--){
    		int a=read();
    		int i=lower_bound(F+1,F+cnt+1,a)-F;
    		if(i>cnt)
    			puts("-1");
    		else printf("%lld\n",flow[i]*a-cost[i]);
    	}
    	return 0;
    }
    
  • 相关阅读:
    Python基础---线程
    virtualenv和virtualenvwrapper的安装和使用
    微信公众号实现简易的物联网控制(二)
    PyQt5之SQLite数据库操作(1)
    PyQt5无边框后窗口的移动方法
    PyQt5显示GIF图片
    Python显示进度条的方法
    WinSCP默认使用root用户登录的方法
    微信公众号实现简易的物联网控制(一)
    Arduino语音天气预报(三)
  • 原文地址:https://www.cnblogs.com/harryzhr/p/16286027.html
Copyright © 2020-2023  润新知