暑假集训csp提高模拟18

赛时 rank21,T1 20pts,T2 0,T3 0,T4 0

学DSU On Tree学傻了,T3寄。

这几次模拟赛一次比一次离谱,一开始还都可以做做,后来一场偶尔有一道不可做,现在直接上俩黑一紫了

T1 Mortis

[ABC302G] Sort from 1 to 4

考虑到排序后的位置是知道的。

我们此时有如下几种情况

  1. \(a,b\)放反(如\(4\;1\)),那么交换一次即可
  2. \(a,b,c\)放反(如\(3\;2\;1\)),那么交换两次即可
  3. \(a,b,c,d\)放反(如\(4\;3\;2\;1\)),那么交换三次即可

贪心的思想肯定是先将所有的情况一放好,然后是二和三。

每个数应该放在哪里我们是知道的,所以枚举一下就好了

点此查看代码
#include<bits/stdc++.h>
#include<bits/extc++.h>
// using namespace __gnu_pbds;
// using namespace __gnu_cxx;
using namespace std;
#define infile(x) freopen(x,"r",stdin)
#define outfile(x) freopen(x,"w",stdout)
#define errfile(x) freopen(x,"w",stderr)
#ifdef LOCAL
    FILE *InFile = infile("in.in"),*OutFile = outfile("out.out");
    // FILE *ErrFile=errfile("err.err");
#else
    FILE *Infile = stdin,*OutFile = stdout;
    //FILE *ErrFile = stderr;
#endif
using ll=long long;using ull=unsigned long long;
using db = double;using ldb = long double;
const int N = 2e5 + 10;
int n,a[N],num[5],L[5],R[5],have[5][5],ans = 0;
vector<int> pos[5];
inline void solve(){
    cin>>n;
    for(int i = 1;i <= n; ++i) 
        cin>>a[i],num[a[i]]++,pos[a[i]].push_back(i);
    for(int i = 1;i <= 4; ++i) L[i] = R[i-1]+1,R[i] = L[i] + num[i]-1;
    for(int i = 1;i <= 4; ++i){
        for(int j = L[i];j <= R[i]; ++j){
            have[a[j]][i]++;
        }
    }
    for(int i = 1;i <= 4; ++i){
        for(int j = 1;j <= 4; ++j){
            if(i == j) continue;
            int k = min(have[i][j],have[j][i]);
            ans += k;
            have[i][j] -= k;
            have[j][i] -= k;
        }
    }
    for(int i = 1;i <= 4; ++i){
        for(int j = 1;j <= 4; ++j){
            if(i == j) continue;
            for(int k = 1;k <= 4; ++k){
                if(k == i || k == j) continue;
                int emm = min({have[j][i],have[k][j],have[i][k]});
                ans += 2*emm;
                have[j][i] -= emm;
                have[k][j] -= emm;
                have[i][k] -= emm;
            }
        }
    }
    for(int i = 1;i <= 4; ++i){
        for(int j = 1;j <= 4; ++j){
            if(i == j) continue;
            for(int k = 1;k <= 4; ++k){
                if(k == i || k == j) continue;
                for(int t = 1;t <= 4; ++t){
                    if(t == i || t == j || t == k) continue;
                    int emm = min({have[j][i],have[k][j],have[t][k],have[i][t]});
                    ans += 3 * emm;
                    have[j][i]-=emm;
                    have[k][j]-=emm;
                    have[t][k]-=emm;
                    have[i][t]-=emm;
                }
            }
        }
    }
    cout<<ans;
}
signed main(){
    cin.tie(nullptr)->sync_with_stdio(false);
    cout.tie(nullptr)->sync_with_stdio(false);
    solve(); 
}

T2 生活在hzoi上

[WC2019] 数树

不会做,放官方题解了

题面锅了,谢罪。

首先我们先考虑一个暴力。枚举第二棵树的形态求方案。

先给他转化一下,容易发现只在一棵树里出现的边是没用的,那么考虑两棵树内都存在的边。而根据定义,如果只保留两棵树内都存在的边,那同一联通块内的节点给的数是一样的。

那么设 \(E_1\) 为第一棵树的边集,\(E_2\) 为第二棵树的边集,总方案数就是 \(y^{n-|E_1\cap E_2|}\)

然后考虑正解。现在答案是这个东西:

\[\sum_{E_2}y^{n-|E_1\cap E_2|} \]

有个憨批式子叫子集反演:

\[f(S)=\sum_{T\subseteq S}\sum_{P\subseteq T}(-1)^{|T|-|P|}f(P) \]

那设个 \(f(S)=y^{n-|S|}\) 推式子:

\[\begin{aligned} &\sum_{E_2}y^{n-|E_1\cap E_2|}\\ =&\sum_{E_2}f(E_1\cap E_2)\\ =&\sum_{E_2}\sum_{T\subseteq E_1\cap E_2}\sum_{P\subseteq T}(-1)^{|T|-|P|}f(P)\\ =&\sum_{E_2}\sum_{T\subseteq E_1\cap E_2}\sum_{P\subseteq T}(-1)^{|T|-|P|}y^{n-|P|}\\ =&\sum_{E_2}\sum_{T\subseteq E_1\cap E_2}y^{n-|T|}\sum_{P\subseteq T}(-y)^{|T|-|P|}\\ =&\sum_{E_2}\sum_{T\subseteq E_1\cap E_2}y^{n-|T|}\sum_{i=0}^{|T|}\binom{|T|}i(-y)^{|T|-i}\\ =&\sum_{E_2}\sum_{T\subseteq E_1\cap E_2}y^{n-|T|}(1-y)^{|T|}\\ =&\sum_{T\subseteq E_1}y^{n-|T|}(1-y)^{|T|}g(T) \end{aligned} \]

其中 \(g(T)\) 为包含边集 \(T\) 的树个数。由 prufer 序列容易得到

\[g(T)=n^{k-2}\prod_{i=1}^ka_i \]

其中 \(k\) 是连通块个数(也就是 \(n-|T|\)), \(a_i\) 是第 \(i\) 个连通块大小。

那么代回原式:

\[\begin{aligned} &\sum_{T\subseteq E_1}y^{n-|T|}(1-y)^{|T|}g(T)\\ =&\sum_{T\subseteq E_1}y^k(1-y)^{n-k}n^{k-2}\prod_{i=1}^ka_i\\ =&\frac{(1-y)^n}{n^2}\sum_{T\subseteq E_1}\prod_{i=1}^k\frac{ny}{1-y}a_i \end{aligned} \]

先特判掉 \(y=1\)

考虑一下怎么算后边一堆东西。设个 \(dp_{x,i}\)\(x\) 的子树内 \(x\) 所在连通块大小为 \(i\) 的答案,转移考虑在 \(x\) 处枚举子树 \(v\)\((x,v)\) 这条边选不选。这样树上背包就是 \(O(n^2)\) 的。

考虑优化。我们 \(dp\) 的瓶颈为第二维枚举联通块的大小,其实有个 trick 可以给他压掉:考虑到扩展Cayley定理里的式子 \(\prod_{i=1}^ka_i\) 的意义,是每个连通块里面选一个标记点连上,把联通块的贡献变成在联通块内选一个标记点的贡献,即每个联通块有且仅有一个标记点。如果选了标记点那么产生了一个有贡献的联通块,贡献要乘上 \(\dfrac ny{1-y}\)

那么设 \(dp_{x,0/1}\)\(x\) 子树内 \(x\) 所在联通块是否选了点的贡献,转移有:

初值:\(dp_{x,0}=1,dp_{x,1}=\dfrac ny{1-y}\)

转移:

  1. \(dp_{x,1}\):两种情况,一种是和下边的儿子成为一个联通块,另一个是断开儿子的边(儿子的联通块必须选了标记点)。那么就是 \(dp_{x,1}\times dp_{v,0}+dp_{x,0}\times dp_{v,1}+dp_{x,1}\times dp_{v,1}\)

  2. \(dp_{x,0}\):也是要么断开要么不断。断开必须儿子选了,不断必须儿子没选。那么就是 \(dp_{x,0}\times dp_{v,0}+dp_{x,0}\times dp_{v,1}\)

updata on 2024-08-11 17:39:06 星期日:

贺官方题解被jijidawangD了,于是把jijidawang的贺下来了

首先注意到对于这个限制实际上可以只考虑\((p,q)\)是树边的情况,那么相当于将树划分若干个连通块,然后连一些边连通这些连通块(这里不能连原有的树边),假设连通块个数是\(k\)那么贡献\(y^k\).

考虑如果没有不能选原有的树边的限制就是一个\(Prüfer\)序列,答案是\(n^{k−2}∏s_i​\),其中\(s_i\)是每个连通块大小 . 考虑这种计算方式相当于是每个合法方案贡献:

\[\sum_{i=0}\dbinom{n-k}{i}y^{k+i}=y^k(1+y)^{n−k} \]

对于这个式子的解释就是钦定选\(i\)个树边计算有多少个选连通块的方案 .

那么先提出来\((1+y)^n\),然后解方程\(v^k(1+v)^{−k}=yk\) 把贡献改成\(v^k\)就可以天然得到答案 . 从而问题变成一个大小为\(s\)的连通块贡献\(vs\),一棵树的贡献是连通块贡献的乘积,求所有树的贡献和 . 注意到相当于在每个连通块里面选一个点然后贡献\(v\),用一个简单树形 DP 即可计算 .

T3 嘉然今天吃什么

[Ynoi Easy Round 2021] TEST_68

全场第二道可做题还是Ynoi的虽然是Easy Round

\(\Huge{6}\)

一开始以为是Dsu On Tree,结果糖了一场以后还是感觉不可做。

这道题勉强算是部分分启发正解(反正没有启发我)

异或最大,考虑\(01-Trie\)

我们通过分析性质观察样例发现,很多点的答案都是全局最大值。

那么先将全局最大值处理出来,然后考虑异或后为最大值的两个点\(x,y\)

发现只有根到这两个点的简单路径上的点的答案才有可能不是全局最大值。

从上往下暴力跳链,统计答案即可。

点此查看代码
#include<bits/stdc++.h>
#include<bits/extc++.h>
// using namespace __gnu_pbds;
// using namespace __gnu_cxx;
using namespace std;
#define infile(x) freopen(x,"r",stdin)
#define outfile(x) freopen(x,"w",stdout)
#define errfile(x) freopen(x,"w",stderr)
#ifdef LOCAL
    FILE *InFile = infile("in.in"),*OutFile = outfile("out.out");
    // FILE *ErrFile=errfile("err.err");
#else
    FILE *Infile = stdin,*OutFile = stdout;
    //FILE *ErrFile = stderr;
#endif
using ll=long long;using ull=unsigned long long;
using db = double;using ldb = long double;
const int N = 5e5 + 10;
class Trie{
private:
    int tree[N*60][3],ed[N*60],tot;
public:
    inline void insert(ll x){
        int p = 0;
        for(int i = 60; i >= 0; --i){
            int k = (x>>i)&1;
            if(!tree[p][k]) tree[p][k] = ++tot;
            p = tree[p][k];
        }
    }
    inline ll query(ll x){
        int p = 0;
        ll sum = 0;
        for(int i = 60; i >= 0; --i){
            int k = (x>>i)&1;
            if(tree[p][k^1]) sum += (1ll<<i),p = tree[p][k^1];
            else p = tree[p][k];
        }
        return sum;
    }
    inline void clear(){
        memset(tree,0,sizeof tree);
        tot = 0;
    }
}T;
struct EDGE{int to,next;}edge[N<<1];
int head[N],cnt;
inline void add(int u,int v){
    edge[++cnt] = {v,head[u]};
    head[u] = cnt;
}
int dfn[N],rdfn[N],tot,bg[N],ed[N];
int n,fa[N],x,y;
void dfs(int x){
    rdfn[dfn[x] = bg[x] = ++tot] = x;
    for(int i = head[x]; i;i = edge[i].next){
        int y = edge[i].to;
        if(y == fa[x]) continue;
        dfs(y);
    }
    ed[x] = tot;
}
ll a[N],mx,ans[N],now;
inline void solve(){
    cin>>n;
    for(int i = 2;i <= n; ++i) cin>>fa[i],add(fa[i],i);
    for(int i = 1;i <= n; ++i) cin>>a[i],T.insert(a[i]);
    for(int i = 1;i <= n; ++i){
        ll res = T.query(a[i]);
        if(res > mx) mx = res,x = i;
    }
    for(int i = 1;i <= n; ++i)
        if((a[x] ^ a[i]) == mx){y = i;break;}
    dfs(1);
    vector<int> p;memset(ans,-1,sizeof ans);
    int id = x;T.clear();
    while(x) p.emplace_back(x),x = fa[x];
    reverse(p.begin(),p.end());
    for(int i = 0;i + 1 < p.size(); ++i){
        int u = p[i],v = p[i + 1];ans[u] = now;
        T.insert(a[u]);now = max(now,T.query(a[u]));
        for(int j = head[u]; j;j = edge[j].next){
            int t = edge[j].to;
            if(t == fa[u] || t == v) continue;
            for(int k = bg[t];k <= ed[t]; ++k){
                T.insert(a[rdfn[k]]);
                now = max(now,T.query(a[rdfn[k]]));
            }
        }
    }
    ans[id] = now;now = 0,id = y;T.clear();
    vector<int> ().swap(p);
    while(y) p.emplace_back(y),y = fa[y];
    reverse(p.begin(),p.end());
    for(int i = 0;i + 1 < p.size(); ++i){
        int u = p[i],v = p[i + 1];ans[u] = now;
        T.insert(a[u]);now = max(now,T.query(a[u]));
        for(int j = head[u]; j;j = edge[j].next){
            int t = edge[j].to;
            if(t == fa[u] || t == v) continue;
            for(int k = bg[t];k <= ed[t]; ++k){
                T.insert(a[rdfn[k]]);
                now = max(now,T.query(a[rdfn[k]]));
            }
        }
    }
    ans[id] = now;
    for(int i = 1;i <= n; ++i) cout<<(ans[i] == -1?mx:ans[i])<<'\n';

}
signed main(){
    cin.tie(nullptr)->sync_with_stdio(false);
    cout.tie(nullptr)->sync_with_stdio(false);
    solve();    
}

T4 APJifengc

[AGC036D] Negative Cycle

不会,贺的官方题解。

负环想到差分约束。设第 \(i\) 个点对应 \(x_i\)

\(q_i=x_i-x_{i+1}\),首先根据初始的边可以知道 \(q_i\ge 0\)

对于边权为 \(-1\) 的边 \(i\rightarrow j\),有 \(x_i-x_j\ge 1\),也就是 \(q_i+q_{i+1}+\dots+q_{j-1}\ge 1\)

边权为 \(1\) 同理,有 \(x_j-x_i\le 1\),也就是 \(q_j+q_{j+1}+\dots+q_{i-1}\le 1\)

假如我们知道 \(q_i\),那么边权为 \(-1\) 的边在区间和 \(\ge 1\) 时留下,边权为 \(1\) 的边在区间和 \(\le 1\) 时留下。也就是如果一段 \(q_i=0\),那么这段的边权为 \(-1\) 的边就要删掉,如果区间和 \(\ge 2\) 那么边权为 \(1\) 的边就要删掉。

如果 \(q_i\ge 2\),那么显然不如 \(1\) 好。于是 \(q_i=0/1\)

\(dp_{i,j}\) 为扫到 \(i\),最后一个 \(1\)\(q_i\),倒数第二个是 \(q_j\) 的最小代价,转移可以从 \(dp_{j,k}\) 转移,系数可以二维前缀和。复杂度 \(O(n^3)\)

posted @ 2024-08-11 16:19  CuFeO4  阅读(8)  评论(0编辑  收藏  举报