AtCoder Grand Contest 031
链接
D. A Sequence of Permutations
考虑 \(f(p,q)=pq^{-1}\)。枚举一下有:
肉眼可见有 \(qp^-q^-p\) 一直保留,所以本质上是一个 \(6\) 的循环节。
那么直接快速幂即可。
复杂度 \(O(n\log k)\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 100010
using namespace std;
struct perm{
int a[N];
perm(){memset(a,0,sizeof(a));}
int& operator [](int x){return a[x];}
};
int n,k;
perm inv(perm &a){perm b;for(int i=1;i<=n;i++) b[a[i]]=i;return b;}
perm r,c;
perm operator *(perm a,perm b){for(int i=1;i<=n;i++) c[i]=a[b[i]];return c;}
perm ksm(perm a,int b)
{
for(int i=1;i<=n;i++) r[i]=i;
for(;b;b>>=1,a=a*a) if(b&1) r=r*a;
return r;
}
perm p,q,t,ans;
int main()
{
scanf("%d%d",&n,&k);--k;
for(int i=1;i<=n;i++) scanf("%d",&p[i]);
for(int i=1;i<=n;i++) scanf("%d",&q[i]);
t=q*inv(p)*inv(q)*p;
ans=p;
for(int i=1;i<k%6;i++) ans=q,q=q*inv(p),p=ans;
ans=ksm(t,k/6)*ans*ksm(inv(t),k/6);
for(int i=1;i<=n;i++) printf("%d ",ans[i]);
return 0;
}
E. Snuke the Phantom Thief
考虑行列分开算,可以发现一个 \(\leq a_i\) 个数不超过 \(b_i\) 限制,可以看做第 \(b_i+1\) 个宝石必须 \(>a_i\)。假设总共拿了 \(k\) 颗宝石,那么一个 \(\geq a_i\) 个数不超过 \(b_i\) 限制可以看做第 \(b_{k-i}\) 个宝石必须 \(<a_i\)。
枚举 \(k\),那么每个宝石等价于有一个 rank 的限制,排名必须在 \([l,r]\) 区间。
然后行列建点,行可行区间向宝石连边,宝石向列可行区间连边,每个宝石独自限流 \(1\),跑最大费用最大流即可。
复杂度 \(O(n^3m)\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef __int128 lll;
const int N=30010,M=400010,inf=1000000000;
const ll winf=1000000000000000000ll;
struct road{
int nxt,to,f;
ll w;
}r[M<<1];
int head[N],cnt=1;
void add(int u,int v,int f,ll w)
{
r[++cnt]=(road){head[u],v,f,w};head[u]=cnt;
r[++cnt]=(road){head[v],u,0,-w};head[v]=cnt;
}
namespace MCMF{
ll dis[N];
int all,fl[N],pre[N],bef[N];
bool in[N];
queue<int>q;
bool spfa(int s,int t)
{
for(int i=1;i<=all;i++) dis[i]=-winf,fl[i]=inf,in[i]=false;
dis[s]=0; pre[t]=-1;in[s]=true;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
in[u]=false;
for(int i=head[u];i;i=r[i].nxt)
if(r[i].f)
{
int v=r[i].to;
if(dis[v]<dis[u]+r[i].w)
{
dis[v]=dis[u]+r[i].w;
fl[v]=min(fl[u],r[i].f);
pre[v]=u,bef[v]=i;
if(!in[v]) in[v]=true,q.push(v);
}
}
}
return pre[t]!=-1;
}
int maxf;ll minw;
void work(int s,int t)
{
while(spfa(s,t))
{
maxf+=fl[t],minw+=fl[t]*dis[t];
for(int u=t;u!=s;u=pre[u])
r[bef[u]].f-=fl[t],r[bef[u]^1].f+=fl[t];
}
}
void clear()
{
for(int i=1;i<=all;i++) head[i]=dis[i]=0;
all=0;cnt=1;
maxf=minw=0;
}
}
inline void chkmin(int &x,int y){x=min(x,y);}
inline void chkmax(int &x,int y){x=max(x,y);}
int x[N],y[N];ll w[N];
int px[N],py[N],po[N];
int s,t,tt,n,m;
int lx[N],rx[N],ly[N],ry[N];
ll ans=0;
void solve(int k)
{
auto id=[&](int x,int d){return x+2*k+d*n;};
s=id(n,1)+1,t=s+1;MCMF::all=t;
for(int i=1;i<=k;i++) add(s,i,1,0),add(i+k,t,1,0);
for(int i=1;i<=n;i++) add(id(i,0),id(i,1),1,w[i]),lx[i]=ly[i]=1,rx[i]=ry[i]=100;
for(int i=1;i<=m;i++)
if(po[i]==0 && py[i]+1<=k) chkmax(lx[py[i]+1],px[i]+1);
else if(po[i]==1 && py[i]+1<=k) chkmin(rx[k-py[i]],px[i]-1);
else if(po[i]==2 && py[i]+1<=k) chkmax(ly[py[i]+1],px[i]+1);
else if(po[i]==3 && py[i]+1<=k) chkmin(ry[k-py[i]],px[i]-1);
for(int i=2;i<=k;i++) chkmax(lx[i],lx[i-1]),chkmax(ly[i],ly[i-1]);
for(int i=k-1;i;i--) chkmin(rx[i],rx[i+1]),chkmin(ry[i],ry[i+1]);
for(int i=1;i<=k;i++) for(int j=1;j<=n;j++) if(lx[i]<=x[j] && x[j]<=rx[i]) add(i,id(j,0),1,0);
for(int i=1;i<=k;i++) for(int j=1;j<=n;j++) if(ly[i]<=y[j] && y[j]<=ry[i]) add(id(j,1),i+k,1,0);
MCMF::work(s,t);
if(MCMF::maxf==k) ans=max(ans,MCMF::minw);
MCMF::clear();
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d%d%lld",&x[i],&y[i],&w[i]);
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
char op[3];
scanf("%s%d%d",op,&px[i],&py[i]);
po[i]=(op[0]=='L'?0:(op[0]=='R'?1:(op[0]=='D'?2:3)));
}
for(int i=1;i<=n;i++) solve(i);
printf("%lld\n",ans);
return 0;
}
F. Walk on Graph
神仙题。
首先可以发现如果一个点周围有边 \(a\),那么来回走就能 \(x\rightarrow 4x+3a\)。如果同时有 \(x\rightarrow 4x+3b\),由于 \(4\) 在奇数模数下有逆元,所以有 \(x\rightarrow x+(a-b)\)。
不妨令 \(g=\gcd\{\Delta w\}\),那么 \(x\) 可以任意加上 \(3qg,q\in \Z\)。这样可以令 \(\text{mod}'=\gcd(\text{mod},3g)\)。
考虑将每个数字的状态用 \((p,q)=px+qg\) 表示,其中 \(p\) 是 \(2\) 的次幂。可以发现 \(q\in[0,2]\),然后显然有 \((px,qg)\rightarrow (4px,4qg)=(4px,qg)\),所以 \(p\in[1,2]\)。
这样总共只有 \(6\) 种状态,总共 \(6n\) 个点,直接枚举每条边对应点连边即可。
考虑答案,可以发现最后要从状态 \((p,q)\) 走到 \((0,0)\),而且 \((p,q)\) 需要能表示 \(r\),这个可以直接预处理,复杂度 \(O(\text{mod})\)。
总复杂度 \(O(\text{mod}+(n+m+q)\log n)\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 600010
using namespace std;
int gcd(int x,int y){return y==0?x:gcd(y,x%y);}
int x[N],y[N],w[N],f[N];
int find(int x){return x==f[x]?f[x]:(f[x]=find(f[x]));}
void merge(int x,int y){x=find(x);y=find(y);if(x!=y) f[x]=y;}
bool pre[2][1000010];
int main()
{
int n,m,q,mod,g=0;
scanf("%d%d%d%d",&n,&m,&q,&mod);
for(int i=1;i<=m;i++) scanf("%d%d%d",&x[i],&y[i],&w[i]);
for(int i=2;i<=m;i++) g=gcd(g,abs(w[i]-w[1]));
if(!g) g=mod;
else mod=gcd(mod,g*3);
int w0=w[1]%g;
auto id=[&](int u,int p,int q){return (u-1)*6+p*3+q;};
for(int i=0,j=w0;i<mod*2;i++,j=j*2%mod) pre[i&1][j]=true;
for(int i=1;i<=6*n;i++) f[i]=i;
for(int i=1;i<=m;i++)
for(int p=0;p<2;p++)
for(int q=0;q<3;q++)
merge(id(x[i],p,q),id(y[i],!p,(2*q+(w[i]-w0)/g)%3)),
merge(id(y[i],p,q),id(x[i],!p,(2*q+(w[i]-w0)/g)%3));
auto check=[&](int s,int t,int l)
{
for(int p=0;p<2;p++)
for(int q=0;q<3;q++)
if(find(id(s,p,q))==find(id(t,0,0)) && pre[p][(l+w0+(3-q)*g)%mod]) return true;
return false;
};
while(q --> 0)
{
int s,t,l;scanf("%d%d%d",&s,&t,&l);
puts(check(s,t,l)?"YES":"NO");
}
return 0;
}

浙公网安备 33010602011771号