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\) 上一个出现的位置,转移是分成染色或者不染转移即可:
#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)\) 的本质:
然后大力列出 \(a_i\) 的前若干项。
令 \(A=qp^{-1}q^{-1}p\),则可以发现:\(a_n=Aa_{n-6}A^{-1}\ (n>6)\)。
推一波式子可以推出:
#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)\),即所有的边的状态都是可以互相到达的。因为每个 \(x\) 都有唯一对应的 \(2x+w\) 以及唯一对应的 \(\frac{x-w}{2}\),即每个点的出度和入度均为 \(1\)。
考虑如果有 \((u,v,a),(u,w,b)\) 的边,则有
则 \((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)\) 能到达的状态都可以用 \((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;
}