#11 CF1019C & CF679E & CF1017G
Sergey's problem
题目描述
解法
美好的一天从构造题开始,虽然我还是做不来。
考虑先满足所有点都能在两步之内走到的限制,可以按这样的方法构造出点集 \(V\):选取一个点 \(u\),然后删除所有满足存在有向边 \(u\rightarrow v\) 的点 \(v\),持续这个过程直到不能操作。
思考这样构造出来点集 \(V\) 的性质,它满足所有点可以通过选取点一步之内走到,但是根据构造的特点,可能后选取的点向先选取的点连了边。但是考虑到两步之内走到的限制更为宽松,我们可以在此基础上调整。
考虑到 选取点的导出子图是 \(\tt DAG\),我们用拓扑排序来调整。对于选取点 \(v\),如果存在 \(u\) 被保留并且 \(u\rightarrow v\),那么 \(v\) 就可以不必被保留;否则 \(v\) 就需要被保留。这样不会产生选取点的冲突,并且所有点都可以在两步之内走到,时间复杂度 \(O(n)\)
总结
本题展示了调整法的应用,存在两个需要满足的条件,可以让初状态先满足一个条件,然后再调整。此时初状态的选取依然重要,如果把 \(V\) 设置为全集那么使用调整法是困难的。
其实本题从另一个角度切入,如果题目保证了图是 \(\tt DAG\),那么很容易想到上面不产生冲突并且所有点都可以在一步之内走到的方法。那么本题就是放宽了第二个限制让你解决一般图的情形,考虑如何通过适当的"代价"转化成 \(\tt DAG\) 即可。
所以对于图论问题:从简单结构扩展到复杂结构,仍是一个值得积累的思维方式。
#include <cstdio>
#include <vector>
#include <iostream>
#include <queue>
using namespace std;
const int M = 1000005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,in[M],vis[M],dp[M],d[M];vector<int> ans,g[M];
signed main()
{
n=read();m=read();
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
g[u].push_back(v);
}
for(int i=1;i<=n;i++) if(!vis[i])
{
vis[i]=in[i]=1;
for(int x:g[i]) vis[x]=1;
}
for(int u=1;u<=n;u++) if(in[u])
for(int v:g[u]) if(in[v])
d[v]++;
queue<int> q;
for(int i=1;i<=n;i++) if(!d[i] && in[i])
q.push(i);
while(!q.empty())
{
int u=q.front();q.pop();
if(!dp[u]) ans.push_back(u);
for(int v:g[u]) if(in[v])
{
dp[v]|=(dp[u]^1);
if(--d[v]==0) q.push(v);
}
}
printf("%d\n",ans.size());
for(int x:ans) printf("%d ",x);
}
Bear and Bad Powers of 42
题目描述
解法
考虑到 \(42\) 的次幂在值域中很少很少(只需要处理到 \(42^{11}\) 就完全足够了),感受到三操作如果暴力修改复杂度也是很对的,现在问题只是判断:区间中是否存在 \(42\) 的次幂。
但是判断区间是否存在某个数是困难的,我们不妨把条件放宽一点,考察区间中所有越过 \(42\) 次幂的数,因为 \(42\) 的次幂很少所以复杂度还是很对的。在这些越过的数中,如果有数恰好落在 \(42\) 的次幂上,那么就再次进行操作 \(3\)
具体来说就是线段树上维护 和下一个 \(42\) 次幂的距离 的最小值,操作 \(2\) 可以暴力区间赋值,操作 \(3\) 在最小值 \(\geq x\) 或者存在赋值标记的时候打标记,否则暴力递归下去。
复杂度是很容易通过势能法分析的,我们定义势能为:区间所有颜色段上方的 \(42\) 次幂个数之和。那么一次 \(2\) 操作会增加 \(O(\log n\log_{42} v)\) 的势能,\(3\) 操作消耗 \(O(1)\) 的时间会解决 \(O(1)\) 的势能,所以时间复杂度 \(O(n\log n\log_{42}v)\)
总结
线段树上尽量不要去维护有关特定值的信息,如果需要维护常常可以通过放宽条件的方法解决。
#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
const int M = 400005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,z[20],mn[M],t1[M],t2[M],t3[M];
int fd(int x) {return lower_bound(z+1,z+11,x)-z;}
void cov(int x,int c1,int c2)
{
mn[x]=t1[x]=c1;t2[x]=c2;t3[x]=0;
}
void add(int x,int c)
{
if(!t2[x]) {mn[x]-=c;t3[x]+=c;return ;}
t1[x]-=c;
while(t1[x]<0) t1[x]+=z[t2[x]+1]-z[t2[x]],t2[x]++;
mn[x]=t1[x];
}
void down(int i)
{
if(t2[i])
cov(i<<1,t1[i],t2[i]),cov(i<<1|1,t1[i],t2[i]),
t1[i]=t2[i]=0;
if(t3[i])
add(i<<1,t3[i]),add(i<<1|1,t3[i]),t3[i]=0;
}
void col(int i,int l,int r,int L,int R,int c1,int c2)
{
if(L>r || l>R) return ;
if(L<=l && r<=R) {cov(i,c1,c2);return ;}
int mid=(l+r)>>1;down(i);
col(i<<1,l,mid,L,R,c1,c2);
col(i<<1|1,mid+1,r,L,R,c1,c2);
mn[i]=min(mn[i<<1],mn[i<<1|1]);
}
int upd(int i,int l,int r,int L,int R,int c)
{
if(L>r || l>R) return z[11];
if(L<=l && r<=R && (mn[i]>=c || t2[i]))
{add(i,c);return mn[i];}
int mid=(l+r)>>1;down(i);
upd(i<<1,l,mid,L,R,c);
upd(i<<1|1,mid+1,r,L,R,c);
return mn[i]=min(mn[i<<1],mn[i<<1|1]);
}
int ask(int i,int l,int r,int id)
{
if(l==r) return z[t2[i]]-t1[i];
int mid=(l+r)>>1;down(i);
if(mid>=id) return ask(i<<1,l,mid,id);
return ask(i<<1|1,mid+1,r,id);
}
signed main()
{
n=read();m=read();z[0]=1;
for(int i=1;i<=10;i++) z[i]=z[i-1]*42;
for(int i=1;i<=n;i++)
{
int x=read();
col(1,1,n,i,i,z[fd(x)]-x,fd(x));
}
for(int i=1;i<=m;i++)
{
int op=read(),l=read();
if(op==1)
{
printf("%lld\n",ask(1,1,n,l));
continue;
}
int r=read(),x=read();c++
if(op==2) col(1,1,n,l,r,z[fd(x)]-x,fd(x));
else while(!upd(1,1,n,l,r,x));
}
}
The Tree
题目描述
解法
手切了一道 *3200
的数据结构题,还是不错的 ( ̄_, ̄ )
由于一操作看起来有很好的性质,可以给出一些 \(\tt observation\):一个点的颜色只与其祖先的操作情况有关,并且只和哪些点操作了多少次有关,和点的操作顺序无关。
那么可以把一操作这样转化:我们把所有点的初始点权设置为 \(-1\),然后一操作就相当于把某个点的权值加上 \(1\),那么点 \(u\) 显黑色的充要条件是,存在一个权值 \(\geq 0\) 的后缀。
那么可以用树链剖分简单维护后缀最大值。我们再来翻译操作二,首先要清空 \(x\) 子树内的所有新增权值,把点权区间覆盖成 \(-1\) 即可,然后要保证子树都为黑,就要去除祖先的影响,设后缀最大值是 \(val\),那么我们把点 \(x\) 的权值减少 \(val+1\)
时间复杂度 \(O(n\log ^2 n)\)
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const int M = 100005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,cnt,siz[M],son[M],top[M],num[M],dep[M],fa[M];
vector<int> g[M];int fl[M<<2];
struct node
{
int sum,mx;
node(int A=0,int B=0) : sum(A) , mx(B) {}
node operator + (const node &b) const
{return node(sum+b.sum,max(b.mx,b.sum+mx));}
}t[M<<2];
void dfs1(int u,int p)
{
siz[u]=1;fa[u]=p;
dep[u]=dep[p]+1;
for(int v:g[u]) if(v^p)
{
dfs1(v,u);
siz[u]+=siz[v];
if(siz[son[u]]<siz[v]) son[u]=v;
}
}
void dfs2(int u,int tp)
{
top[u]=tp;num[u]=++cnt;
if(son[u]) dfs2(son[u],tp);
for(int v:g[u]) if(v^fa[u] && v^son[u])
dfs2(v,v);
}
void clr(int i,int x)
{
t[i]=node(-x,-1);fl[i]=1;
}
void down(int i,int l,int r)
{
if(!fl[i]) return ;
int mid=(l+r)>>1;
clr(i<<1,mid-l+1);
clr(i<<1|1,r-mid);
fl[i]=0;
}
void add(int i,int l,int r,int id,int c)
{
if(l==r) {t[i].sum+=c;t[i].mx=t[i].sum;return ;}
int mid=(l+r)>>1;down(i,l,r);
if(mid>=id) add(i<<1,l,mid,id,c);
else add(i<<1|1,mid+1,r,id,c);
t[i]=t[i<<1]+t[i<<1|1];
}
void cov(int i,int l,int r,int L,int R)
{
if(l>R || L>r) return ;
if(L<=l && r<=R) {clr(i,r-l+1);return ;}
int mid=(l+r)>>1;down(i,l,r);
cov(i<<1,l,mid,L,R);
cov(i<<1|1,mid+1,r,L,R);
t[i]=t[i<<1]+t[i<<1|1];
}
node ask(int i,int l,int r,int L,int R)
{
if(L<=l && r<=R) return t[i];
int mid=(l+r)>>1;down(i,l,r);
if(mid<L) return ask(i<<1|1,mid+1,r,L,R);
if(R<=mid) return ask(i<<1,l,mid,L,R);
return ask(i<<1,l,mid,L,R)+ask(i<<1|1,mid+1,r,L,R);
}
int qry(int x)
{
node r=node(0,-1);
while(x)
{
r=ask(1,1,n,num[top[x]],num[x])+r;
x=fa[top[x]];
}
return r.mx;
}
signed main()
{
n=read();m=read();
for(int i=2;i<=n;i++)
g[read()].push_back(i);
dfs1(1,0);dfs2(1,1);
for(int i=1;i<=n;i++)
add(1,1,n,i,-1);
for(int i=1;i<=m;i++)
{
int op=read(),x=read();
if(op==3)
{
if(qry(x)>=0) puts("black");
else puts("white");
}
if(op==1) add(1,1,n,num[x],1);
if(op==2)
{
cov(1,1,n,num[x],num[x]+siz[x]-1);
add(1,1,n,num[x],-1-qry(x));
}
}
}