【学习笔记】CF1322
智商不够,要回炉重造了
没猜出来结论,是不是该退役了
既然没做出来,那么还是要写一下题解。记与右部点 i i i相邻的点的集合为 S i S_i Si,如果 S i = S j S_i=S_j Si=Sj那么合并成一组,其权值为 c i + c j c_i+c_j ci+cj。最后答案是不同组的 c i c_i ci的 gcd \text{gcd} gcd。
这条结论的证明显然是不那么显然的,不过作为 
     
      
       
       
         oier 
        
       
      
        \text{oier} 
       
      
    oier要求的能力应该是把它给猜出来。但是当你自己做这道题的时候又怎会有信仰相信这里有一条结论呢?
证明也不难。当然我自己没有独立去想 。先把每个节点除以 
     
      
       
       
         gcd 
        
       
      
        \text{gcd} 
       
      
    gcd,然后相当于证对于任意 
     
      
       
       
         k 
        
       
         > 
        
       
         1 
        
       
      
        k>1 
       
      
    k>1,存在一个 
     
      
       
       
         S 
        
       
      
        S 
       
      
    S满足 
     
      
       
       
         f 
        
       
         ( 
        
       
         S 
        
       
         ) 
        
       
      
        f(S) 
       
      
    f(S)不被 
     
      
       
       
         K 
        
       
      
        K 
       
      
    K整除。如果 
     
      
       
       
         ∑ 
        
        
        
          c 
         
        
          i 
         
        
       
      
        \sum c_i 
       
      
    ∑ci不被 
     
      
       
       
         K 
        
       
      
        K 
       
      
    K整除那么显然得证,否则取出度数最小的点 
     
      
       
       
         i 
        
       
      
        i 
       
      
    i,考虑将与其相邻的点从 
     
      
       
       
         S 
        
       
      
        S 
       
      
    S中删去,那么只有 
     
      
       
       
         i 
        
       
      
        i 
       
      
    i从 
     
      
       
       
         N 
        
       
         ( 
        
       
         S 
        
       
         ) 
        
       
      
        N(S) 
       
      
    N(S)中被删去了,如果 
     
      
       
        
        
          c 
         
        
          i 
         
        
       
      
        c_i 
       
      
    ci不被 
     
      
       
       
         K 
        
       
      
        K 
       
      
    K整除那么显然又得证了,否则就不断重复这个过程,直到有一个点不被 
     
      
       
       
         K 
        
       
      
        K 
       
      
    K整除为止。显然此时至少还剩 
     
      
       
       
         1 
        
       
      
        1 
       
      
    1个点,因此最后剩下来的 
     
      
       
       
         S 
        
       
      
        S 
       
      
    S不为空。
#include<bits/stdc++.h>
#define pb push_back
#define fi first
#define se second
#define ll long long
#define db double
#define inf 0x3f3f3f3f
using namespace std;
const int N=5e5+5;
int n,m,T;
ll c[N],v[N];
vector<int>g[N];
map<ll,ll>dp;
ll gcd(ll x,ll y){
    return y==0?x:gcd(y,x%y);
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0); 
	cin>>T;mt19937 t(114514);
    while(T--){
        cin>>n>>m;
        for(int i=1;i<=n;i++)cin>>c[i],g[i].clear();
        for(int i=1;i<=m;i++){
            int x,y;
            cin>>x>>y,g[y].pb(x);
        }
        for(int i=1;i<=n;i++)v[i]=t()%(1ll<<60);
        dp.clear();
        for(int i=1;i<=n;i++){
            ll x=0;
            for(auto j:g[i]){
                x^=v[j];
            }
            if(g[i].size()){
                dp[x]+=c[i];
            }
        }
        ll res=0;
        for(auto it:dp){
            res=gcd(res,it.se);
        }
        cout<<res<<"\n";
    }
}
非常标准的 
      
       
        
        
          d 
         
        
          p 
         
        
       
         dp 
        
       
     dp题,所以教我如何快速切掉它。
先不考虑时间复杂度,应该如何计算贡献?
显然每个位置的贡献与跨过这个位置的点的数目有关,因此只要记录一下当前剩余点的数目就能算。因此我们将 l i l_i li从小到大进行处理,也就是倒序 d p dp dp。
然而,我犯了一个 非常致命的错误 :我把进位的过程想错了。究其本质,是读题的问题,我没有将死掉的那个选手给删除掉。
事实上这道题目非常简单。有一个非常技巧性的做法:设 
     
      
       
       
         d 
        
        
        
          p 
         
         
         
           i 
          
         
           , 
          
         
           j 
          
         
        
       
      
        dp_{i,j} 
       
      
    dpi,j表示当前级别为 
     
      
       
       
         i 
        
       
      
        i 
       
      
    i的人还剩 
     
      
       
       
         j 
        
       
      
        j 
       
      
    j个时的最大收益。考虑 
     
      
       
       
         d 
        
        
        
          p 
         
         
         
           i 
          
         
           , 
          
         
           j 
          
         
        
       
      
        dp_{i,j} 
       
      
    dpi,j能被转移到所满足的条件,显然就直接做到 
     
      
       
       
         O 
        
       
         ( 
        
        
        
          n 
         
        
          2 
         
        
       
         log 
        
       
          
        
       
         n 
        
       
         ) 
        
       
      
        O(n^2\log n) 
       
      
    O(n2logn)了,可以直接通过本题。真的吗。
不过这玩意可以用神必的方式优化到 
     
      
       
       
         O 
        
       
         ( 
        
        
        
          n 
         
        
          2 
         
        
       
         ) 
        
       
      
        O(n^2) 
       
      
    O(n2)。观察题解代码应该不难理解。
#include<bits/stdc++.h>
#define pb push_back
#define fi first
#define se second
#define ll long long
#define db double
#define inf 0x3f3f3f3f
using namespace std;
const int N=5000;
const int M=2005;
int n,m,l[M],s[M],c[N],dp[N+M][M],ans;
void chmax(int &x,int y){
    x=max(x,y);
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0); 
	cin>>n>>m;memset(dp,-0x3f,sizeof dp);
    for(int i=1;i<=n+m;i++)dp[i][0]=0;
    for(int i=1;i<=n;i++)cin>>l[i];
    for(int i=1;i<=n;i++)cin>>s[i];
    for(int i=1;i<=n+m;i++)cin>>c[i];
    for(int i=n;i>=1;i--){
        for(int j=n;j>=1;j--){
            chmax(dp[l[i]][j],dp[l[i]][j-1]+c[l[i]]-s[i]);
        }
        for(int j=l[i];j<=n+m;j++){
            for(int k=0;k<=n>>min(20,j-l[i]);k++){
                chmax(dp[j+1][k/2],dp[j][k]+(k/2)*c[j+1]);
            }
        }
    }
    cout<<dp[n+m][0];
}
面对*3300的数据结构题,我只能望而兴叹
第一步都想错了。。。
考虑只有 0 / 1 0/1 0/1的情况,答案就是最长的 01 01 01段长度除以 2 2 2。
对于这道题,任取一个 Mid \text{Mid} Mid,将 ≤ Mid \le\text{Mid} ≤Mid的位置看成 0 0 0, > Mid >\text{Mid} >Mid的位置看成 1 1 1,只要取遍所有的 Mid \text{Mid} Mid,求答案的最大值就是第一问的答案。
对于第二问,考虑 
     
      
       
       
         Mid 
        
       
      
        \text{Mid} 
       
      
    Mid从小到大取遍时,第一次出现 
     
      
       
       
         0 
        
       
      
        0 
       
      
    0就是这个位置所对应的权值。好像有更聪明的方法,但是我不清楚
接下来的一步比较运气。考虑其差分序列,有一些比较好的性质,比如一段连续的 1 1 1就代表了一段 01 01 01交错的区间,所以第一问的答案就是连续段 1 1 1长度的最大值 / 2 /2 /2;对于 0 0 0相当于是不动点,因此我们用 set \text{set} set维护这些不动点的位置,然后区间覆盖即可。
复杂度 O ( n log  n ) O(n\log n) O(nlogn)。
还是想的有点久,希望下次能更快一点
常数大的离谱,而且调的也有点久,考场上估计时间就不够了吧。
#include<bits/stdc++.h>
#define pb push_back
#define fi first
#define se second
#define ll long long
#define db double
#define inf 0x3f3f3f3f
using namespace std;
const int N=5e5+5;
int n,a[N],b[N],lsh[N],cnt,res[N],ans;
set<int>s,s2;
int get(int x){return lower_bound(lsh+1,lsh+1+cnt,x)-lsh;}
vector<int>point[N];
struct node{
    int tag,val;
}t[N<<2];
void add(int p,int Mid){
    if(!t[p].tag){
        t[p].tag=1,t[p].val=Mid;
    }
}
void pushdown(int p){
    if(t[p].tag){
        add(p<<1,t[p].val),add(p<<1|1,t[p].val);
    }
}
int qry(int p,int l,int r,int x){
    if(l==r){
        return t[p].val?t[p].val:inf;
    }
    int mid=l+r>>1;
    pushdown(p);
    return x<=mid?qry(p<<1,l,mid,x):qry(p<<1|1,mid+1,r,x);
}
void upd3(int p,int l,int r,int ql,int qr,int Mid){
    if(ql>qr)return;
    if(ql<=l&&r<=qr){
        add(p,Mid);
        return;
    }
    pushdown(p);
    int mid=l+r>>1;
    if(ql<=mid)upd3(p<<1,l,mid,ql,qr,Mid);
    if(mid<qr)upd3(p<<1|1,mid+1,r,ql,qr,Mid);
}
void upd2(int l,int r,int Mid){
    auto it=s.lower_bound(l-1),it2=s.lower_bound(r);
    if(++it!=it2){
        return;
    }
    ans=max(ans,(r-l)/2);
    if(r-l&1){
        if(b[l]==0){
            upd3(1,1,n,l+1,(l+r)/2,Mid);
        }
        else{
            upd3(1,1,n,(l+r)/2+1,r-1,Mid);
        }
    }
    else{
        if(b[l]==0){
            upd3(1,1,n,l+1,r-1,Mid);
        }
    }
}
void del(int x){
    auto it=s.lower_bound(x),it2=it;
    s2.erase(x);
    if(++(it2=it)!=s.end()){
        s2.insert(*it2);
    }
    if(it!=s.begin()){
        s2.insert(*--(it2=it));
    }
    s.erase(x);
}
void ins(int x){
    auto it=s.lower_bound(x),it2=it;
    if(it!=s.end()){
        s2.insert(*it);
    }
    if(it!=s.begin()){
        s2.insert(*--(it2=it));
    }
    s.insert(x);
    s2.insert(x);
}
void upd(int x){
    b[x]=0;
    if(x<n&&b[x]==b[x+1]){
        ins(x);
    }
    if(x<n&&b[x]!=b[x+1]){
        del(x);
    }
    if(x>1&&b[x]==b[x-1]){
        ins(x-1);
    }
    if(x>1&&b[x]!=b[x-1]){
        del(x-1);
    }
}
void mark(int x,int y){
    if(!res[x])res[x]=y;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
    cin>>n;for(int i=1;i<=n;i++)cin>>a[i],lsh[i]=a[i];
    sort(lsh+1,lsh+1+n),cnt=unique(lsh+1,lsh+1+n)-lsh-1;
    for(int i=1;i<=n;i++)b[i]=1;
    for(int i=1;i<=n;i++)point[get(a[i])].pb(i);
    for(int i=0;i<=n;i++)s.insert(i);
    for(int i=1;i<=cnt;i++){
        s2.clear();
        for(int j=0;j<point[i].size();j++)upd(point[i][j]);
        int l=-1;
        for(auto it:s2){
            if(b[it]==0){
                mark(it,lsh[i]),mark(it+1,lsh[i]);
            }
            if(~l){
                upd2(l+1,it,lsh[i]);
            }
            l=it;
        }
    }
    cout<<ans<<"\n";
    for(int i=1;i<=n;i++){
        if(i==1||i==n)cout<<a[i]<<" ";
        else cout<<min(qry(1,1,n,i),res[i])<<" ";
    }
}
算是比较友好的一道题
首先对于每个点,设 a i a_i ai为 0 0 0表示 c i < c f a i c_i<c_{fa_i} ci<cfai, a i a_i ai为 1 1 1表示 c i > c f a i c_i>c_{fa_i} ci>cfai,如果已知 { a i } \{a_i\} {ai},那么再二分答案 Mid \text{Mid} Mid,显然我们可以树 d p dp dp判断是否合法。
显然 { a i } , { c i } \{a_i\},\{c_i\} {ai},{ci}需要同时考虑并转移,这也是这道题麻烦的地方。
我们发现对于 
     
      
       
       
         { 
        
        
        
          a 
         
        
          i 
         
        
       
         } 
        
       
      
        \{a_i\} 
       
      
    {ai}有两种限制:
  
     
      
       
       
         1.1 
        
       
      
        1.1 
       
      
    1.1  
     
      
       
        
        
          a 
         
        
          i 
         
        
       
         = 
        
        
        
          a 
         
         
         
           f 
          
          
          
            a 
           
          
            i 
           
          
         
        
       
      
        a_i=a_{fa_i} 
       
      
    ai=afai
  
     
      
       
       
         1.2 
        
       
      
        1.2 
       
      
    1.2 对于两个兄弟 
     
      
       
       
         i 
        
       
         , 
        
       
         j 
        
       
      
        i,j 
       
      
    i,j, 
     
      
       
        
        
          a 
         
        
          i 
         
        
       
         ≠ 
        
        
        
          a 
         
        
          j 
         
        
       
      
        a_i\ne a_j 
       
      
    ai=aj(这是 
     
      
       
       
         L 
        
       
         C 
        
       
         A 
        
       
      
        LCA 
       
      
    LCA处的限制)
那么我们定义, d p i dp_i dpi表示当 a i a_i ai为 0 0 0时的 c i c_i ci的最小值,显然将整颗子树的 { a i } \{a_i\} {ai}取反就能得到 a i a_i ai为 1 1 1时 c i c_i ci的最大值为 Mid − d p i + 1 \text{Mid}-dp_i+1 Mid−dpi+1。因此,知道了儿子的 { a i } \{a_i\} {ai}就能推出 d p i dp_i dpi。
观察上述限制,由于我们固定了 a i = 0 a_i=0 ai=0,因此对于第一类限制可以直接解决了。对于第二类限制,我们可以进行一个分组。对于没有固定的组,可以让其中一组全为 0 0 0,而另一组全为 1 1 1,那么我们观察,所有 [ x + 1 , Mid − y ] ∪ [ y + 1 , Mid − x ] [x+1,\text{Mid}-y]\cup [y+1,\text{Mid}-x] [x+1,Mid−y]∪[y+1,Mid−x]的交当中最小的那个点就是我们的答案。因为这两个区间是对称的,所以可以做到线性转移。
复杂度 O ( n log  n ) O(n\log n) O(nlogn)。
唉,不知不觉这道题就写了半天,我真是太菜了
代码非常丑,不过好歹还是补完了
很难评价这道题,因为码量确实有点大,考场上无论如何都打不出来吧
其实我也在想,遇到这样复杂一点的问题,以我的码力真的能够完成吗?
常数大的离谱。
#include<bits/stdc++.h>
#define pb push_back
#define fi first
#define se second
#define ll long long
#define db double
#define inf 0x3f3f3f3f
using namespace std;
const int N=5e5+5;
int n,m,U[N],V[N],f[N][20],dep[N];
vector<int>g[N];
int Lca(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    for(int i=19;i>=0;i--)if(dep[f[x][i]]>=dep[y])x=f[x][i];
    if(x==y)return x;
    for(int i=19;i>=0;i--)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
    return f[x][0];
}
void dfs(int u,int topf){
    dep[u]=dep[topf]+1;
    f[u][0]=topf;for(int i=1;i<=19;i++)f[u][i]=f[f[u][i-1]][i-1];
    for(auto v:g[u]){
        if(v!=topf){
            dfs(v,u);
        }
    }
}
int fa[N*2],cf[N],cf2[N],dp[N];
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void unionset(int x,int y){
    int u=find(x),v=find(y);
    if(u!=v)fa[u]=v;
}
void dfs2(int u,int topf){
    for(auto v:g[u]){
        if(v!=topf){
            dfs2(v,u),cf[u]+=cf[v],cf2[u]+=cf2[v];
        }
    }
    if(cf[u]){
        unionset(u,topf),unionset(u+n,topf+n);
    }
}
int ok,task,vis[N],c[2],C[N];
int state[N];
vector<int>G[N];
vector<int>point[2];
void dfs4(int u,int col){
    c[col]=max(c[col],dp[u]),C[u]=col,vis[u]=task;
    point[col].pb(u);
    //cout<<u<<"\n";
    for(auto v:G[u]){
        if(vis[v]!=task){
            dfs4(v,col^1);
        }
    }
}
struct node{
    int c[2];
    vector<int>point[2];
}now;
vector<node>vec;
int dfs3(int u,int topf,int Mid){
    state[u]=0;
    int L=1,R=Mid,L2=1,R2=Mid;
    for(auto v:g[u]){
        if(v!=topf&&!dfs3(v,u,Mid)){
            return 0;
        }
    }
    for(auto v:g[u]){
        //fixed
        if(v!=topf&&cf2[v]&&vis[v]!=task&&cf[v]){
            c[0]=c[1]=0;
            point[0].clear(),point[1].clear();
            dfs4(v,0);
            c[0]++,c[1]++;
            if(c[0]+c[1]>Mid+1)return 0;
            for(auto x:point[1])state[x]^=1;
            L2=max(L2,c[0]),R2=min(R2,Mid-c[1]+1);
        }
    }
    vec.clear();
    for(auto v:g[u]){
        if(v!=topf&&cf2[v]&&vis[v]!=task){
            c[0]=c[1]=0;
            point[0].clear(),point[1].clear();
            dfs4(v,0);
            c[0]++,c[1]++;
            if(c[0]+c[1]>Mid+1)return 0;
            if(c[0]>c[1])swap(c[0],c[1]),swap(point[0],point[1]);
            now.c[0]=c[0],now.c[1]=c[1];
            now.point[0]=point[0],now.point[1]=point[1];
            vec.pb(now);
            if(Mid-c[1]+1>=Mid/2+1){
                L=max(L,c[0]),R=min(R,Mid-c[0]+1);
            }
            else{
                L=max(L,c[0]),R=min(R,Mid-c[1]+1);
            }
        }
    }
    if(max(L,L2)<=min(R,R2)){
        dp[u]=max(L,L2);
    }
    else if(max(Mid-R+1,L2)<=min(Mid-L+1,R2)){
        dp[u]=max(Mid-R+1,L2);
    }
    else{
        return 0;
    }
    for(auto &v:vec){
        if(v.c[0]<=dp[u]&&dp[u]<=Mid-v.c[1]+1){
            for(auto &x:v.point[1]){
                state[x]^=1;
            }
        }
        else{
            assert(v.c[1]<=dp[u]&&dp[u]<=Mid-v.c[0]+1);
            for(auto &x:v.point[0]){
                state[x]^=1;
            }
        }
    }
    return 1;
}
int ans[N];
void dfs5(int u,int topf,int Mid){
    if(state[u])ans[u]=dp[u];
    else ans[u]=Mid-dp[u]+1;
    for(auto v:g[u]){
        if(v!=topf){
            state[v]^=state[u],dfs5(v,u,Mid);
        }
    }
}
int check(int Mid){
    task++;
    return dfs3(1,0,Mid);
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
    cin>>n>>m;for(int i=1;i<=2*n;i++)fa[i]=i;
    for(int i=1;i<n;i++){
        int x,y;
        cin>>x>>y,g[x].pb(y),g[y].pb(x);
    }
    dfs(1,0);
    for(int i=1;i<=m;i++){
        int u,v;cin>>u>>v;
        int lca=Lca(u,v);
        cf[u]++,cf[v]++;
        cf2[u]++,cf2[v]++,cf2[lca]-=2;
        for(int j=19;j>=0;j--){
            if(dep[f[u][j]]>dep[lca])u=f[u][j];
            if(dep[f[v][j]]>dep[lca])v=f[v][j];
        }
        cf[u]--,cf[v]--;
        if(u!=lca&&v!=lca){
            unionset(u,v+n),unionset(u+n,v);
            G[u].pb(v),G[v].pb(u);
        }
    }
    dfs2(1,0);
    for(int i=1;i<=n;i++){
        if(find(i)==find(n+i)){
            cout<<-1<<"\n";
            return 0;
        }
    }
    int l=1,r=n,res=0;
    while(l<=r){
        int mid=l+r>>1;
        if(check(mid))res=mid,r=mid-1;
        else l=mid+1;
    }
    cout<<res<<"\n";
    check(res);
    dfs5(1,0,res);
    for(int i=1;i<=n;i++)cout<<ans[i]<<" ";
}

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号