AtCoder Grand Contest 031

A - Colorful Subsequence

可以发现每个字符选哪个是独立的,每个字符可以选择任意一个或者不选,令 \(c_{\texttt{a}}\) 表示 \(\texttt{a}\) 出现的次数,答案即为 \(\left(\sum\limits_{i=\texttt{a}}^{\texttt{b}} (c_i+1)\right)-1\)


#include<iostream>
#include<cstdio>
using namespace std;
const int N=100005;
const int MOD=1000000007;
int n;
char s[N];
int cnt[26];
int main()
{
	scanf("%d",&n);
	scanf("%s",s+1);
	for(int i=1;i<=n;i++)
		cnt[s[i]-'a']++;
	long long ans=1;
	for(int i=0;i<26;i++)
		ans=ans*(cnt[i]+1)%MOD;
	ans--;
	printf("%lld",ans);
	return 0;
}

B - Reversi

\(f_i\) 表示前 \(i\) 个位置的不同颜色方案。令 \(lst_c\) 表示颜色 \(c\) 上一个出现的位置,转移是分成染色或者不染转移即可:

\[f_i=f_{i-1}+[lst_{c_i}< i-1]f_{lst_{c_i}} \]


#include<iostream>
#include<cstdio>
using namespace std;
const int N=200005;
const int MOD=1000000007;
int n;
int c[N];
int lst[N];
long long dp[N];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&c[i]);
	dp[0]=1;
	for(int i=1;i<=n;i++)
	{
		dp[i]=dp[i-1];
		if(lst[c[i]]&&lst[c[i]]<i-1) dp[i]=(dp[i]+dp[lst[c[i]]])%MOD;
		lst[c[i]]=i;
	}
	printf("%lld",dp[n]);
	return 0;
}

C - Differ by 1 Bit

可以证明,如果 \(A\)\(B\) 二进制下不同的位数如果为偶数无解,可以用数学归纳法证明。

考虑构造 \(A\)\(B\) 二进制下不同的位数为奇数的情况。从高到低考虑每一个二进制位,构造对于当前区间的左端点需要为 \(a\),右端点需要为 \(b\),当前为二进制下第 \(k\) 位,需要填在答案的 \([l,r]\) 内。

如果 \(a,b\) 的第 \(k\) 位不同,我们可以将 \([l,mid]\) 的第 \(k\) 位与 \(a\) 相同,\([mid+1,r]\) 的第 \(k\) 位与 \(b\) 相同。我们可以确定一个 \(p_{mid}\),需要满足 \(p_{mid}\)\(0\sim k-1\) 位与 \(b\) 不同,且 \(p_{mid}\)\(a,b\)\(0\sim k-1\) 位不同的位数为奇数,然后递归填即可。

如果 \(a,b\) 的第 \(k\) 位相同,一种构造方法是 \([l+1,mid+1]\) 的第 \(k\) 位与 \(a,b\) 不同,其他 \([mid+2,r]\)\(l\) 的第 \(k\) 位与 \(a,b\) 相同。我们可以先将 \([mid+1,r]\) 的第 \(0\sim k-1\) 位填上,且 \(p_{mid+2}\)\(0\sim k-1\) 位要和 \(a\) 不同。然后再将 \([l+1,mid+1]\) 填上即可。


#include<iostream>
#include<cstdio>
using namespace std;
const int N=(1<<20)+5; 
int n,A,B;
int res[N];
void solve(int k,int a,int b,int l,int r)
{
	if(__builtin_popcount(a^b)%2==0)
	{
		printf("NO");
		exit(0);
	}
	if(k==0)
	{
		res[l]=a,res[r]=b;
		return;
	}
	int mid=(l+r)/2;
	int m=(1<<k)-1;
	if((a&(1<<k))==(b&(1<<k)))
	{
		solve(k-1,a&m,b&m,mid+1,r);
		solve(k-1,a&m,res[mid+2],l+1,mid+1);
		if(a&(1<<k))
		{
			for(int i=mid+2;i<=r;i++)
				res[i]|=1<<k;
			res[l]=a;
		}
		else
		{
			for(int i=l+1;i<=mid+1;i++)
				res[i]|=1<<k;
			res[l]=a;
		}
	}
	else
	{
		solve(k-1,a&m,(b&m)^1,l,mid);
		solve(k-1,res[mid],b&m,mid+1,r);
		if(a&(1<<k))
		{
			for(int i=l;i<=mid;i++)
				res[i]|=1<<k;
		}
		else
		{
			for(int i=mid+1;i<=r;i++)
				res[i]|=1<<k;
		}
	}
	return;
}
int main()
{
	scanf("%d%d%d",&n,&A,&B);
	solve(n-1,A,B,0,(1<<n)-1);
	printf("YES\n");
	for(int i=0;i<(1<<n);i++)
		printf("%d ",res[i]);
	return 0;
}

D - A Sequence of Permutations

定义置换乘法 \(pq\) 表示先经过 \(q\) 的映射再经过 \(p\) 的映射,则 \(i\to p_{q_i}\)

考虑 \(f(p,q)\) 的本质:

\[[f(p,q)]_{p_i}=q_i\to f(p,q)p=q\to f(p,q)=qp^{-1} \]

然后大力列出 \(a_i\) 的前若干项。

\[\begin{aligned}a_1&=p \\ a_2&=q \\ a_3&=qp^{-1} \\ a_4&=qp^{-1}q^{-1} \\ a_5&=qp^{-1}q^{-1}pq^{-1} \\ a_6&=qp^{-1}q^{-1}ppq^{-1} \\ a_7&=qp^{-1}q^{-1}pqpq^{-1} \\ a_8&=qp^{-1}q^{-1}pqp^{-1}qpq^{-1}\end{aligned} \]

\(A=qp^{-1}q^{-1}p\),则可以发现:\(a_n=Aa_{n-6}A^{-1}\ (n>6)\)

推一波式子可以推出:

\[a_n=A^{\lfloor \frac{k-1}{6}\rfloor }\cdot a_{(k-1)\bmod 6+1}\cdot A^{-1 \cdot \lfloor \frac{k-1}{6}\rfloor } \]


#include<iostream>
#include<cstdio>
#include<cassert>
#include<vector>
using namespace std;
const int N=100005;
typedef vector<int> Permutation;
int n,k;
Permutation p,q;
Permutation a[7];
Permutation operator*(const Permutation &p,const Permutation &q)
{
	assert(p.size()==q.size());
	Permutation res(p.size());
	for(size_t i=1;i<res.size();i++)
		res[i]=p[q[i]];
	return res;
}
Permutation getinv(const Permutation &p)
{
	Permutation res(p.size());
	for(size_t i=1;i<p.size();i++)
		res[p[i]]=i;
	return res;
}
Permutation operator/(const Permutation &p,const Permutation &q)
{
	return p*getinv(q);
}
Permutation ksm(Permutation a,int b)
{
	Permutation res(a.size());
	for(size_t i=1;i<res.size();i++)
		res[i]=i;
	while(b)
	{
		if(b&1) res=res*a;
		a=a*a,b>>=1;
	}
	return res;
}
int main()
{
	scanf("%d%d",&n,&k);
	p.resize(n+1),q.resize(n+1);
	for(int i=1;i<=n;i++)
		scanf("%d",&p[i]);
	for(int i=1;i<=n;i++)
		scanf("%d",&q[i]);
	a[1]=p,a[2]=q;
	for(int i=3;i<=6;i++)
		a[i]=a[i-1]/a[i-2];
	Permutation A=q/p/q*p;
	Permutation res=ksm(A,(k-1)/6)*a[(k-1)%6+1]*ksm(getinv(A),(k-1)/6);
	for(int i=1;i<=n;i++)
		printf("%d ",res[i]);
	return 0;
}

E - Snuke the Phantom Thief

首先有一个转化,如果限制 \(\geq a_i\) 的珠宝只能选 \(b_i\) 个,考虑枚举选择的珠宝个数 \(k\),限制即为选第 \(1\sim k-b_i\) 小的珠宝 \(< a_i\)。限制 \(\leq a_i\) 的珠宝只能选 \(b_i\) 个,可以转化为选的第 \(b_i+1\sim k\) 小珠宝必须 \(> a_i\)

考虑一维怎么做,建一排 \(k\) 的点,将源点 \(S\) 向这些点连一条流量为 \(1\),费用为 \(0\) 的边。第 \(i\) 个点表示选的第 \(i\) 小的珠宝。每个点有一个限制 \([l_i,r_i]\) 表示第 \(i\) 小的点需要 \(> l_i\)\(< r_i\),将 \(i\) 向满足限制的点连一条流量为 \(1\),费用为 \(0\) 的边。再将所有的珠宝向汇点 \(T\) 连一条流量为 \(1\),费用为 \(v_i\) 的边,跑最大费用最大流即可。

二维无非是比一维多了一个限制,只要将每个珠宝拆成两个点,两个点之间连流量为 \(1\),费用为 \(v_i\) 的边即可,然后将两边连上两个限制的点即可。


#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
const int N=85,M=325;
const int INF=1061109567;
const long long LINF=4557430888798830399;
int n,m;
int x[N],y[N];
long long v[N];
char t[M];
int a[M],b[M];
struct Edge
{
	int to,next;
	long long cost,flow;
}edge[N*N*6];
int head[N*4],cnt=1;
void add_edge(int u,int v,long long c,int f)
{
	cnt++;
	edge[cnt].to=v;
	edge[cnt].next=head[u];
	edge[cnt].cost=c;
	edge[cnt].flow=f;
	head[u]=cnt;
	return;
}
void add(int u,int v,long long c,int f)
{
	add_edge(u,v,c,f);
	add_edge(v,u,-c,0);
	return;
}
int S,T;
long long dis[N*4];
bool spfa()
{
	static bool vis[N*4];
	memset(vis,false,sizeof(vis));
	for(int i=0;i<N*4;i++)
		dis[i]=-LINF;
	queue<int>q;
	dis[S]=0;
	vis[S]=true;
	q.push(S);
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		vis[u]=false;
		for(int i=head[u];i;i=edge[i].next)
		{
			int v=edge[i].to;
			if(edge[i].flow<=0) continue;
			if(dis[v]<dis[u]+edge[i].cost)
			{
				dis[v]=dis[u]+edge[i].cost;
				if(!vis[v])
				{
					vis[v]=true;
					q.push(v);
				}
			}
		}
	}
	return dis[T]!=-LINF;
}
bool book[N*4];
int cur[N*4];
pair<long long,long long>dfs(int u,long long flow)
{
    if(u==T||flow==0) return make_pair(flow,0);
    book[u]=true;
    long long used=0,res=0;
    for(int &i=cur[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(book[v]||dis[v]!=dis[u]+edge[i].cost||edge[i].flow<=0) continue;
        pair<long long,long long>t=dfs(v,min(flow,edge[i].flow));
        long long now=t.first;
        res+=t.second+now*edge[i].cost;
        flow-=now;
        edge[i].flow-=now;
        edge[i^1].flow+=now;
        used+=now;
        if(flow==0) break;
    }
    book[u]=false;
    return make_pair(used,res);
}
pair<long long,long long> dinic()
{
    long long ans=0,Min=0;
    while(spfa())
    {
        memcpy(cur,head,sizeof(head));
        pair<long long,long long> res=dfs(S,INF);
        ans+=res.first,Min+=res.second;
    }
    return make_pair(ans,Min);
}
int lr[N],rr[N],lc[N],rc[N];
long long solve(int k)
{
	for(int i=1;i<=m;i++)
		if(t[i]=='L')
		{
			if(b[i]+1<=k) lr[b[i]+1]=max(lr[b[i]+1],a[i]);
		}
		else if(t[i]=='R')
		{
			if(k-b[i]>=1) rr[k-b[i]]=min(rr[k-b[i]],a[i]);
		}
		else if(t[i]=='D')
		{
			if(b[i]+1<=k) lc[b[i]+1]=max(lc[b[i]+1],a[i]);
		}
		else if(t[i]=='U')
		{
			if(k-b[i]>=1) rc[k-b[i]]=min(rc[k-b[i]],a[i]);
		}
	for(int i=2;i<=k;i++)
		lr[i]=max(lr[i],lr[i-1]),lc[i]=max(lc[i],lc[i-1]);
	for(int i=k-1;i>=1;i--)
		rr[i]=min(rr[i],rr[i+1]),rc[i]=min(rc[i],rc[i+1]);
	S=0,T=n+n+k+k+1;
	for(int i=1;i<=n;i++)
		add(i,i+n,v[i],1);
	for(int i=1;i<=k;i++)
		for(int j=1;j<=n;j++)
			if(x[j]>lr[i]&&x[j]<rr[i]) add(i+n+n,j,0,1);
	for(int i=1;i<=k;i++)
		for(int j=1;j<=n;j++)
			if(y[j]>lc[i]&&y[j]<rc[i]) add(j+n,i+n+n+k,0,1);
	for(int i=1;i<=k;i++)
		add(S,i+n+n,0,1),add(i+n+n+k,T,0,1);
	long long res=dinic().second;
	for(int i=1;i<=k;i++)
		lr[i]=lc[i]=0,rr[i]=rc[i]=INF;
	cnt=1;
	for(int i=0;i<=n+n+k+k+1;i++)
		head[i]=0;
	return res;
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>x[i]>>y[i]>>v[i];
	cin>>m;
	for(int i=1;i<=m;i++)
		cin>>t[i]>>a[i]>>b[i];
	for(int i=1;i<=n;i++)
		lr[i]=lc[i]=0,rr[i]=rc[i]=INF;
	long long ans=0;
	for(int k=1;k<=n;k++)
		ans=max(ans,solve(k));
	cout<<ans;
	return 0;
}

F - Walk on Graph

考虑从后往前走,问题变成了:

一个人在 \(t\) 点,有一个数 \(x\) 初值为 \(0\)。每当他走过一条长度为 \(w\) 的边,\(x\) 就会变成 \((2x+w)\bmod mod\),每次询问是否存在一条路径使得从 \(t\) 走到 \(s\)\(x=r\)

一个暴力的想法是对于状态 \((u,x)\),表示当前点为 \(u\),权值为 \(x\)。如果有边 \((u,v,w)\),则 \((u,x)\) 可以到达 \((v,2x+w)\),暴力建边判断 \((u,0)\) 是否能到达 \((v,r)\) 即可。

考虑一条边 \((u,v,w)\),有

\[(u,x)\to(v,2x+w)\to(u,4x+3w)\to(v,8x+7w)\to\cdots \to (u,x) \]

最后一定可以回到状态 \((u,x)\),即所有的边的状态都是可以互相到达的。因为每个 \(x\) 都有唯一对应的 \(2x+w\) 以及唯一对应的 \(\frac{x-w}{2}\),即每个点的出度和入度均为 \(1\)

考虑如果有 \((u,v,a),(u,w,b)\) 的边,则有

\[(u,x)\Leftrightarrow (v,2x+a)\Leftrightarrow (u,4x+3a) \]

\[(u,x)\Leftrightarrow (w,2x+b)\Leftrightarrow (u,4x+3b) \]

\((u,4x+3a)\Leftrightarrow (u,4x+3b)\),将 \(4x+3a\) 视为 \(x\),则有 \((u,x)\Leftrightarrow (u,x+3(b-a))\)。即 \((u,x)\Leftrightarrow (u,x+3k(b-a))\)

\(g_u\) 表示与 \(u\) 相邻的所有的边权之差在模域下\(\gcd\),则 \((u,x)\Leftrightarrow (u,x+3kg_u)\),因为上面的 \((u,x)\Leftrightarrow (u,x+3k(b-a))\) 可以看做辗转相除的过程。

可以发现,对于两个点 \(u,v\)\((u,x)\Leftrightarrow (u,x+3kg_v)\),因为 \((u,x)\Leftrightarrow (v',2x+c')\Leftrightarrow \cdots \Leftrightarrow (v,2^px+q) \Leftrightarrow (v,2^px+q+2^p\cdot 3g_v) \Leftrightarrow \cdots \Leftrightarrow (u,x+3kg_v)\)

\(G\) 表示所有的 \(g_u\)\(\gcd\),则 \(\forall u,x,k,(u,x)\Leftrightarrow (u,x+3kG)\)。那么可以将 \(mod\) 改成 \(\gcd(mod,3G)\)。可以发现,改后的 \(mod\) 肯定为 \(G\)\(3G\),因为 \(G|(a-b)\)\(G|mod-(a-b)\),则 \(G|mod\),即 \(\gcd(mod,3G)\)\(G\)\(3G\)

可以发现,所有的边 \(C\) 都可以表示成 \(C=cG+b\) 的形式,后面的 \(b\) 不太好处理。我们可以将所有的 \(x\) 加上 \(b\),令新的 \(x\)\(x'\),边权减去 \(b\),每次询问 \((t,b)\) 能否到达 \((s,b+r)\),可以发现这样是等价的:

\[(u,x'-b)=(u,x)\Leftrightarrow (u,2x+cG+b)=(u,(2(x+b)+cG)-b)=(2x'+cG)-b \]

\((u,x)\) 能到达的状态都可以用 \((v,2^px+qG)\) 表示。因为 \(mod|3G\),则 \(q\in \{0,1,2\}\)

因为 \((u,x)\Leftrightarrow (v,2x+cG)\Leftrightarrow (u,4x+3cG)=(u,4x)\),所以 \(p\in\{1,2\}\)。有效的 \((v,2^px+qG)\) 只有 \(6n\) 个,其中 \(v\in[1,n],p\in\{1,2\},q\in\{0,1,2\}\)

询问 \((t,b)\) 能否到达 \((s,b+r)\)。相当于我们需要找到一组 \((p,q),p\in N^+,q\in\{0,1,2\}\),满足 \(2^pb+qG=b+r\)\((t,b)\)\((s,(p\bmod 2)b+qG)\) 连通。所以可以预处理出 \([1,mod)\) 中每个数是否等于某个 \(2\) 的奇数/偶数次幂,枚举 \((p,q)\) 判断即可。


#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=50005,M=1000005;
int n,m,Q;
int mod;
int x[N],y[N],z[N];
bool Pw[M][2];
int Get(int x,int y,int z)
{
	return (x-1)*6+y*3+z+1;
}
struct Union_Set
{
	int fa[N*6];
	void init(int n)
	{
		for(int i=1;i<=n;i++)
			fa[i]=i;
		return;
	}
	int getf(int v)
	{
		if(v==fa[v]) return v;
		fa[v]=getf(fa[v]);
		return fa[v];
	}
	void merge(int u,int v)
	{
		int f1=getf(u),f2=getf(v);
		if(f1!=f2) fa[f2]=f1;
		return;
	}
}S;
int main()
{
	scanf("%d%d%d%d",&n,&m,&Q,&mod);
	S.init(6*n);
	int g=mod;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&x[i],&y[i],&z[i]);
		g=__gcd(g,abs(z[i]-z[1]));
	}
	mod=__gcd(mod,3*g);
	int r=z[1]%g;
	for(int i=1;i<=m;i++)
	{
		int c=(z[i]-r)/g%3;
		for(int p=0;p<=1;p++)
			for(int q=0;q<=2;q++)
			{
				S.merge(Get(x[i],p,q),Get(y[i],p^1,(2*q+c)%3));
				S.merge(Get(y[i],p,q),Get(x[i],p^1,(2*q+c)%3));
			}
	}
	int x=r;
	for(int i=0;i<=mod;i++)
		Pw[x][i&1]=true,x=x*2%mod;
	while(Q--)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		bool ans=false;
		for(int p=0;p<=1;p++)
			for(int q=0;q<=2;q++)
				if(Pw[(r+z+(3-q)*g)%mod][p])
				{
					if(S.getf(Get(y,0,0))==S.getf(Get(x,p,q))) ans=true;
				}
		if(ans) printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}
posted @ 2023-09-27 18:53  Zhou_JK  阅读(21)  评论(0)    收藏  举报