Codeforces Global Round 14
链接
A. Phoenix and Gold
随便弄一个顺序。
如果有问题就交换有问题的数字和下一个数字,这样一定是合法的。
复杂度 \(O(n)\)。
B. Phoenix and Puzzle
可以发现两个或者四个等腰直角三角形可以拼成一个正方形。
判断 \(\frac n2\) 或者 \(\frac n4\) 是不是平方数即可。
C. Phoenix and Towers
随便钦定一个顺序,然后考虑每次将当前的方块加到最小的石堆里面。
因为每次加入的方块大小 \(\leq x\),加入前的极差 \(\leq x\),所以加入后极差仍然 \(\leq x\)。
D. Phoenix and Socks
屎题,首先能配对的一定配对。
然后接下来一定先保证所有颜色有偶数个,即奇数的左右配对,不够就拿偶数的凑。如果还不够就只能花两步进行配对。
大力分讨,复杂度 \(O(n)\)
E. Phoenix and Computers
考虑显然有一个 \(O(n^4)\) 的 dp,不过没有什么前途。
考虑如果要求没有计算机被自动开机,那么枚举第一个点的位置,剩下的就是一个组合数。容易得到值为 \(2^{k-1}\)。
用 \(f_{i,j}\) 表示长度为 \(i\),其中有 \(j\) 台手动开机的计算机。
那么枚举最后一轮手动开机的计算机数量,直接转移。复杂度 \(O(n^3)\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define N 410
using namespace std;
int mod;
int fac[N],inv[N];
int ksm(int a,int b=mod-2)
{
int r=1;
for(;b;b>>=1)
{
if(b&1) r=1ll*r*a%mod;
a=1ll*a*a%mod;
}
return r;
}
int _2[N];
int C(int a,int b){return a<b?0:1ll*fac[a]*inv[b]%mod*inv[a-b]%mod;}
void init(int n=N-9)
{
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
inv[n]=ksm(fac[n]);
for(int i=n-1;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod;
for(int i=_2[0]=1;i<=n;i++) _2[i]=2ll*_2[i-1]%mod;
}
int f[N][N];
int main()
{
int n,ans=0;
scanf("%d%d",&n,&mod);
init();
for(int i=1;i<=n;i++) f[i][1]=_2[i-1];
for(int i=3;i<=n;i++)
for(int j=2;j*2<=i+1;j++)
for(int k=1;k<=i-2;k++) f[i][j]=(f[i][j]+1ll*f[k][j-1]*_2[i-k-2]%mod*C(i-j+1,i-k-1)%mod)%mod;
for(int i=1;i*2<=n+1;i++) ans=(ans+f[n][i])%mod;
printf("%d\n",ans);
return 0;
}
F. Phoenix and Earthquake
考虑一个结论:只要 \(\sum c_i\geq (n-1)x\),一定存在解。
证明比较显然,考虑反证,如果存在某一时刻没有边可以连,那么此时的 \(\sum c_i< (n-1)x\)。
既然如此只需要保留一棵树即可。考虑接下来怎么构造。
有一个结论是:在上面有解的条件下,如果剩下的所有点 \(c_i< x\),那么任意的合并方案都是合法的。
同样可以反证,因为每次合并后的子树 \(c_i\) 之和是变小的,如果一条边无法被合并,那么一定无解,但是根据上面结论一定有解,故不存在边无法被合并。
考虑从下往上构造,如果一个点子树合完之后的值与其父亲可以继续合并,那么直接合并。否则说明两个点点权均小于 \(x\),直接以任意顺序加到最后即可。
复杂度 \(O(n)\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 300010
#define ll long long
using namespace std;
int nxt[N<<1],to[N<<1],id[N<<1],head[N],cnt;
void add(int u,int v,int w)
{
nxt[++cnt]=head[u];id[cnt]=w;
to[cnt]=v;head[u]=cnt;
}
int f[N];
int find(int x){return f[x]==x?f[x]:(f[x]=find(f[x]));}
ll s[N],c[N],x;
int ans[N],la=1,ra=N;
void dfs(int u,int p)
{
s[u]=c[u];
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(v==p) continue;
dfs(v,u);
if(s[v]+s[u]>=x) ans[la++]=id[i],s[u]+=s[v]-x;
else ans[ra--]=id[i];
}
}
int main()
{
int n,m;
scanf("%d%d%lld",&n,&m,&x);
ll s=0;
for(int i=1;i<=n;i++) scanf("%d",&c[i]),f[i]=i,s+=c[i];
if(s<x*(n-1)){puts("NO");return 0;}
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
if(find(u)==find(v)) continue;
f[find(u)]=find(v);
add(u,v,i),add(v,u,i);
}
ra=n-1;
dfs(1,0);
puts("YES");
for(int i=1;i<n;i++) printf("%d\n",ans[i]);
return 0;
}
G. Phoenix and Odometers
题意
给定一张有向图,边有边权,问从点 \(v_i\) 出发存不存在一条经过若干点(可以重复)后回到 \(v_i\) 的环,满足总环长 \(w+s_i\equiv 0\pmod{t_i}\)。
题解
一个点要能回到它自己,所走的点一定是强联通分量的点。而在一个强联通分量中一定不存在边没有被任何一个环经过。故考虑包含边 \(u\rightarrow^w v\) 的环,绕这个环从 \(v\) 绕 \(t_i\) 圈后走到 \(u\),那么点权为 \(-w\),故可以认为强联通分量中每个点有反向边,边权为相反数。
这样同时证明的也有存在一种额外边权和为 \(0\) 的方案通过任意两个强联通分量中的环。且强联通分量中任意两个点的答案相同。
按照套路,构造一颗 dfs 树,对于每个返祖边统计环的答案,然后按照斐蜀定理取其 gcd,询问时判断是否整除即可。复杂度 \(O(n\log V+q)\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define ll long long
#define N 200010
using namespace std;
int nxt[N<<1],to[N<<1],len[N<<1],head[N],cnt;
ll gcd(ll x,ll y){return y==0?x:gcd(y,x%y);}
void add(int u,int v,int w)
{
nxt[++cnt]=head[u];
to[cnt]=v;len[cnt]=w;
head[u]=cnt;
}
int dfn[N],low[N],tt;
int ton[N],tp;
bool in[N];
int id[N];
vector<int>g[N];
void dfs(int u)
{
dfn[u]=low[u]=++cnt;
ton[++tp]=u;in[u]=true;
for(int v:g[u])
if(!dfn[v]) dfs(v),low[u]=min(low[u],low[v]);
else if(in[v]) low[u]=min(low[u],dfn[v]);
if(dfn[u]==low[u])
{
id[u]=u;in[u]=false;
for(int p=ton[tp--];p!=u;p=ton[tp--]) id[p]=u,in[p]=false;
}
}
ll w[N];bool vis[N];
void solve(int u,ll &res)
{
vis[u]=true;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(id[v]!=id[u]) continue;
if(vis[v]) res=gcd(res,llabs(w[u]-w[v]+len[i]));
else w[v]=w[u]+len[i],solve(v,res);
}
}
ll res[N];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
g[u].push_back(v);
add(u,v,w),add(v,u,-w);
}
for(int i=1;i<=n;i++)
if(!dfn[i]) dfs(i);
for(int i=1;i<=n;i++)
if(id[i]==i) solve(i,res[i]);
int q;
scanf("%d",&q);
for(int i=1;i<=n;i++)
// if(id[i]==i)printf("%d:%d ",id[i],res[id[i]]);puts("");
while(q --> 0)
{
int v,s,t;scanf("%d%d%d",&v,&s,&t);
if(s==0){puts("YES");continue;}
if((t-s)%gcd(res[id[v]],t)) puts("NO");
else puts("YES");
}
return 0;
}
H. Phoenix and Bits
毒瘤题。
考虑如果只有 xor 怎么做。可以参考线段树分裂写一个 Trie 分裂,然后在分裂后的 Trie 上打标记。然后重新合并即可。这部分复杂度 \(O(n\log n)\)。
对于 and 和 or 其实是本质相同的,故不妨只考虑 and。
按位分类讨论,对于 \(x\) 的每一个 0 位 \(k\),可以发现在 Trie 树上对应就是将第 \(k\) 层的所有 \(1\) 子树合并到 \(0\) 上。
感觉不可做?但是考虑仿照线段树合并,只要保证每个 Trie 合并一次节点数量 \(-1\),复杂度就是正确的。问题就在于快速找到需要合并的节点。
考虑如果一个节点的第 \(k\) 层没有 \(1\) 子树那么什么都不用做。如果只有 \(1\) 子树那么直接打上 xor 的 Tag 即可,剩下的情况必然会导致一次合并。一次合并最多访问 \(O(\log n)\) 个节点,故这部分复杂度是 \(O(n\log^2 n)\)。
考虑如何找到个节点的第 \(k\) 层有没有 \(0/1\) 子树,直接大力 update 一次复杂度是 \(O(\log n)\),而总复杂度就是 \(O(n\log^3 n)\),但事实上可以发现这些操作都可以压位之后位运算处理,常数可以忽略。
总复杂度 \(O(n\log^2 n)\)。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=200010,D=20,M=N*D,Mx=(1<<D)-1;
int n,m;
namespace trie{
int val[M],ls[M],rs[M],tot,tag[M];
int h0[M],h1[M];
void setg(int u,int v,int d=D-1)
{
if(!u || d<0) return;
if(v>>d&1) swap(ls[u],rs[u]);
int p=(h0[u]^h1[u])&v;
h0[u]^=p;h1[u]^=p;
tag[u]^=v;
}
int ton[M],tp;
void del(int u)
{
h0[u]=h1[u]=ls[u]=rs[u]=tag[u]=val[u]=0;
ton[++tp]=u;
}
int node(){return tp?ton[tp--]:++tot;}
void push(int u,int d)
{
if(tag[u]) setg(ls[u],tag[u],d-1),setg(rs[u],tag[u],d-1),tag[u]=0;
}
void upd(int u,int d)
{
val[u]=val[ls[u]]+val[rs[u]];
h0[u]=h0[ls[u]]|h0[rs[u]],h1[u]=h1[ls[u]]|h1[rs[u]];
if(ls[u]) h0[u]|=1<<d;
if(rs[u]) h1[u]|=1<<d;
}
void insert(int &u,int x,int d=D-1)
{
if(!u) u=++tot;
if(d<0){val[u]=1;return;}
if(x>>d&1) insert(rs[u],x,d-1);
else insert(ls[u],x,d-1);
upd(u,d);
}
int merge(int x,int y,int d=D-1)
{
if(!x || !y) return x+y;
if(d<0){val[x]|=val[y];del(y);return x;}
push(x,d);push(y,d);
ls[x]=merge(ls[x],ls[y],d-1);
rs[x]=merge(rs[x],rs[y],d-1);
upd(x,d);del(y);
return x;
}
void split(int u,int k,int &l,int &r,int d=D-1)
{
if(d==-1){l=u;r=0;return;}
if(!u){l=r=0;return;}
push(u,d);
if(k>>d&1) l=u,r=node(),split(rs[u],k,rs[l],rs[r],d-1);
else l=node(),r=u,split(ls[u],k,ls[l],ls[r],d-1);
upd(l,d);upd(r,d);
}
void set_k(int u,int k,int d=D-1)//set all 0 in k to 1
{
if(!u || !(h0[u]>>k&1)) return;
if(!(h1[u]>>k&1)){setg(u,1<<k,d);return;}
push(u,d);
if(d==k){rs[u]=merge(rs[u],ls[u],d-1);ls[u]=0;upd(u,d);return;}
set_k(ls[u],k,d-1);set_k(rs[u],k,d-1);
upd(u,d);
}
void reset_k(int u,int k,int d=D-1)//set all 1 in k to 0
{
if(!u || !(h1[u]>>k&1)) return;
if(!(h0[u]>>k&1)){setg(u,1<<k,d);return;}
push(u,d);
if(d==k){ls[u]=merge(rs[u],ls[u],d-1);rs[u]=0;upd(u,d);return;}
reset_k(ls[u],k,d-1);reset_k(rs[u],k,d-1);
upd(u,d);
}
}
using trie::insert;using trie::merge;using trie::split;
using trie::setg;using trie::set_k;using trie::reset_k;
int root;
void set_xor(int l,int r,int x)
{
int lt=0,rt=0;
if(l) split(root,l-1,lt,root);
split(root,r,root,rt);
setg(root,x);
root=merge(merge(lt,root),rt);
}
void set_or(int l,int r,int x)
{
int lt=0,rt=0;
if(l) split(root,l-1,lt,root);
split(root,r,root,rt);
for(int i=0;i<D;i++) if(x>>i&1) set_k(root,i);
root=merge(merge(lt,root),rt);
}
void set_and(int l,int r,int x)
{
int lt=0,rt=0;
if(l) split(root,l-1,lt,root);
split(root,r,root,rt);
for(int i=0;i<D;i++) if(!(x>>i&1)) reset_k(root,i);
root=merge(merge(lt,root),rt);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1,x;i<=n;i++) scanf("%d",&x),trie::insert(root,x);
while(m --> 0)
{
int op,l,r,x;
scanf("%d%d%d",&op,&l,&r);
if(op==1) scanf("%d",&x),set_and(l,r,x);
else if(op==2) scanf("%d",&x),set_or(l,r,x);
else if(op==3) scanf("%d",&x),set_xor(l,r,x);
else
{
int lt=0,rt=0;
if(l) split(root,l-1,lt,root);
split(root,r,root,rt);
printf("%d\n",trie::val[root]);
root=merge(merge(lt,root),rt);
}
}
return 0;
}
I. Phoenix and Diamonds
咕咕咕

浙公网安备 33010602011771号