10.23 高中模拟3

· · 题解

T1

题意

给定 m 个关系形如 p->q,r,表示在 [l,r] 中,如果[l,k] 满足 q[k+1,r] 满足 r,则 [l,r] 满足 p。问 [1,n] 最后满足几种句法,特别的规定长度为一的序列 x 满足 x

做法

不难发现大区间只会被小区间所影响,区间DP。转移和初始化题目已告诉,设 f(l,r,p) 表示 [l,r] 是否满足 p,枚举中断点 k,枚举 m 种句法匹配,时间复杂度 O(n^3m)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 110;
const int M = 310;
template<typename TY>
inline void read(TY &s){
    ll x = 0,f = 1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    s = x * f;
}
int n,m;
int a[N];
struct node{
    int p,q,r; 
}z[M];
bool vis[N][N]; 
bool f[N][N][M];
//f[l][r][p] 表示 [l,r] 中是否满足语法 p
//时间复杂度 O(n^3m) 空间 O(n^2m) 
void dfs(int l,int r){
    if(vis[l][r]) return;
    vis[l][r] = true;
    for(int k=l;k<r;k++){
        dfs(l,k); dfs(k+1,r);
        for(int i=1;i<=m;i++){
            int ps = z[i].p , qs = z[i].q, rs = z[i].r;
            if(f[l][r][ps]) continue;
            if(f[l][k][qs] && f[k+1][r][rs]) f[l][r][ps] = true;
        }
    }
}
int main(){
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    read(m);
    for(int i=1;i<=m;i++){
        read(z[i].p); read(z[i].q); read(z[i].r);
    }
    read(n);
    for(int i=1;i<=n;i++){
        read(a[i]);
        f[i][i][a[i]] = true;
        vis[i][i] = true;
    }
    dfs(1,n);
    bool flag = false;
    for(int i=1;i<=m;i++){
        if(f[1][n][i]){
            flag = true;
            cout << i << " ";
        } 
    }
    if(!flag) cout << "-1";
    return 0;
}

T2

题意

树上第 k 大菊花? 给定一颗无根树,要求菊花是以一个点为中心,出度至少为 3 的图,问树上前 k 大菊花权值绝对值的异或和。

思路

这是一道很好的关于选前 k 大的题,首先暴力求出每一朵菊花肯定是没有前途的。

先考虑一棵菊花的情况,设我们要求的菊花有 m 条出边,权值最大的菊花,一定是与它相连的节点按权值排完序后,选最前面的 m 个。那我们能不能快速求出前 k 大的来呢,兄弟可以的。

使用多指针+堆来干这件事,我们维护最后的指针 p_1 和倒数第二个指针 p_2,此时我们有两种选择:

  1. 继续移动 p_1
  2. p_1 固定看作边界,p2 变为 p_1 移动,p_3 变为 p_2

将移动的不同情况和权值扔到堆里去维护,每次取最大的更新,这样我们就能保证选出前 k 个一定是前 k 大,且没有重复。

为什么呢?考虑画图证: 注意到每次更新的状态总是小于前驱状态。有人说万一 ade 大于 abf,但是 abf 先被处理怎么办?如果 ade>abf,因为 ade 小于它的前驱状态 ace,所以 ace>abf,所以 ace 一定比 abf 先处理,从而让 ade 也在堆中。

实现

所以我们开一个堆维护每种菊花,只需要记录一下 p_1p_2 的位置,移动时更新权值。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 3e5 + 10;
template<typename TY>
inline void read(TY &s){
    ll x = 0,f = 1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    s = x * f;
}
int n,k;
ll ans,a[N]; 
vector<int> e[N];
struct node{
    int id,l,r,lst;
    ll sum;
};
bool operator<(const node &a,const node &b){
    return a.sum < b.sum;
}
bool cmp(const int &x,const int &y){
    return a[x] > a[y];
}
priority_queue<node> Q; 
int main(){
    freopen("b.in","r",stdin);
    freopen("b.out","w",stdout);
    read(n); read(k);
    for(int i=1;i<=n;i++){
        read(a[i]);
    }
    int u,v;
    for(int i=1;i<n;i++){
        read(u); read(v);
        e[u].push_back(v);
        e[v].push_back(u);
    } 
    for(int i=1;i<=n;i++){
        if(e[i].size() < 3) continue;
        sort(e[i].begin(),e[i].end(),cmp);
        ll res = a[i] + a[e[i][0]] + a[e[i][1]];
        for(int j=2;j<int(e[i].size());j++){
            res += a[e[i][j]];
            Q.push({i,j-1,j,int(e[i].size()),res});
        } 
    }
    while(k--){
        auto x = Q.top(); Q.pop();
        int id = x.id , l = x.l , r = x.r, lst = x.lst;
        ll sum = x.sum;
        ans ^= abs(sum);
        if(x.r + 1 < x.lst) Q.push({id,l,r+1,lst,sum - a[e[id][r]] + a[e[id][r+1]]});
        if(x.l >= 0 && x.l + 1 < x.r) Q.push({id,l-1,l+1,r,sum - a[e[id][l]] + a[e[id][l+1]]});
    }
    cout << ans;
    return 0;
}

T3

题意

给你一颗基环树,要求你将点集分别划分成 k=1-n 个集合。定义分歧边为:

其中 p_i 表示 i 所属的集合。
你需要通过适当的划分,最小化 k=1-n 的分歧边数量。

思路

首先可以倒着考虑,分连通块变为合并连通块。

我们发现最后的答案就是 + 号的数量。我们要最小化分歧边数量,就要先连 + 边,迫不得已连 - 边。

但这是一颗基环树,当连接了环上 s-1 条边时最后一条已经自动连通了,也就是说倒数第二条边连接时一定会有额外的代价:

根据贪心,+ 一定在 - 之前被选中合并其端点。因此第一种情况出现的唯一可能是整个环都是 +,这种情况下我们应当优先把环上的边的端点所在集合合并。 否则环上存在 -,一定对应第二种情况,这时:

并查集+dfs找环,如果一条边两端在已在同一连通块中,说明它是环上的边,这个环就是树上 s->t(s,t)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 3e5 + 10; 
template<typename TY>
inline void read(TY &s){
    ll x = 0,f = 1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    s = x * f;
}
int n;
vector<int> e[N];
int f[N];
int cnt,c0,c1,s,t;
int find(int x){
    if(f[x] != x) f[x] = find(f[x]);
    return f[x];
}
int a[N],cc,ans[N];
bool flag;
void dfs(int u,int fa,int l){
//  cerr << u <<"u\n";
    a[l] = u;
    if(u == t){
        flag = true;
        cc = l;
        return;
    }
    for(auto x : e[u]){
        if(x == fa) continue;
        dfs(x,u,l+1);
        if(flag) return;
    }
}
map<int,map<int,int> > mp;
int main(){
    freopen("c.in","r",stdin);
    freopen("c.out","w",stdout);
    read(n);
    int u,v,w;
    char ch;
    for(int i=1;i<=n;i++){
        f[i] = i;
    } 
    for(int i=1;i<=n;i++){
        read(u); read(v); cin >> ch; 
        if(ch=='+') w = 1,c1++;
        else w = 0,c0++;
        e[u].push_back(v);
        e[v].push_back(u);
        mp[u][v] = mp[v][u] = w; 
        int fu = find(u), fv = find(v);
        if(fu == fv) s = u, t = v;
        else f[fv] = fu;
    }
//  cerr << s << " " << t << "st\n";
    dfs(s,0,1);
    if(mp[s][t]==0) cnt++;
    for(int i=1;i<cc;i++){
//      cerr << a[i] << " ";
        if(mp[a[i]][a[i+1]]==0) cnt++;
    }
    int res = c1;
    ans[n] = c1;
    if(cnt == 0){
        for(int i=n-1;i;i--){
            if(c1){
                c1--; res--; cc--;
                if(cc == 1) res--,c1--,cc--; 
            }
            else{
                c0--; res++; 
            }
            ans[i] = res;
        }
    }
    else if(cnt == 1){
        for(int i=n-1;i;i--){
            if(c1){
                c1--; res--;
                if(!c1) res++,c0--;
            }
            else c0--,res++;
            ans[i] = res;
        }
    }
    else{
        for(int i=n-1;i;i--){
            if(c1){
                c1--; res--;
            }
            else{
                c0--; res++;
                if(c0==1){
                    c0--; res++; 
                }
            }
            ans[i] = res; 
        }
    }
    for(int i=1;i<=n;i++){
        cout << ans[i] << "\n";
    }
    return 0;
}

T4

题意

给定无向完全图,定义权值

val(i,j)=a_i\oplus a_j\oplus w_{lcp(s_i,s_j)}

\sum_{i=1}^{n}\min_{1<=j<=n,j!=i} val(i,j)

暴力

## sub2 $w$ 相等表示边权与两个字符串的lcp无关,式子简化为一个点对 $n$ 个点的最小异或和 ,将 $a_i$ 插入 01trie,贪心查询并且每一次删除和自己相等的 $a$ 值。 ```cpp #include<bits/stdc++.h> #define ll long long #define fi first #define se second using namespace std; const int N = 3e5 + 10; template<typename TY> inline void read(TY &s){ ll x = 0,f = 1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } s = x * f; } int n,m,k; ll w[N],a[N],p[N]; int fa[N],son[N],deep[N],siz[N],top[N]; vector<int> e[N]; void dfs1(int u){ deep[u] = deep[fa[u]] + 1; siz[u] = 1; for(auto x : e[u]){ if(x == fa[u]) continue; fa[x] = u; dfs1(x); siz[u] += siz[x]; if(!son[u]||siz[son[u]] < siz[x]) son[u] = x; } } void dfs2(int u,int topx){ top[u] = topx; if(son[u]) dfs2(son[u],topx); for(auto x : e[u]){ if(x == fa[u] || x == son[u]) continue; dfs2(x,x); } } inline int lca(int x,int y){ while(top[x] != top[y]){ if(deep[top[x]] < deep[top[y]]) swap(x,y); x = fa[top[x]]; } return deep[x] < deep[y] ? x : y; } inline int lcp(int x,int y){ return deep[lca(p[x],p[y])]-1; } struct node{ int siz; int ch[2]; }trie[N*31]; int idx; inline void insert(int x,int v){ int pos = 0; for(int i=31;i>=0;i--){ bool now = (x & (1 << i)); if(!trie[pos].ch[now]){ trie[pos].ch[now] = ++idx; } pos = trie[pos].ch[now]; trie[pos].siz += v; } } inline ll query(int x){ int pos = 0; ll res = 0; for(int i=31;i>=0;i--){ bool now = (x & (1 << i)); if(trie[pos].ch[now] && trie[trie[pos].ch[now]].siz) pos = trie[pos].ch[now]; else if(trie[pos].ch[!now] && trie[trie[pos].ch[!now]].siz){ pos = trie[pos].ch[!now]; res |= (1 << i); } } return res; } int main(){ freopen("d.in","r",stdin); freopen("d.out","w",stdout); bool f = false; read(n); read(m); read(k); for(int i=0;i<=m;i++){ read(w[i]); if(i&&w[i]!=w[i-1]) f = true; } for(int i=1;i<=n;i++){ read(a[i]); } int u,v; char ch; for(int i=1;i<k;i++){ read(u); read(v); cin >> ch; e[u].push_back(v); e[v].push_back(u); } dfs1(1); dfs2(1,1); for(int i=1;i<=n;i++){ read(p[i]); } if(!f){ for(int i=1;i<=n;i++) insert(a[i],1); ll ans = 0; for(int i=1;i<=n;i++){ insert(a[i],-1); ans += query(a[i] ^ w[0]); insert(a[i],1); } cout << ans; return 0; } if(n <= 1000 && m <= 100){ ll ans = 0; for(int i=1;i<=n;i++){//暴力复杂度 O(n^2logm) ll minn = LLONG_MAX; for(int j=1;j<=n;j++){ if(i == j) continue; ll val = a[i] ^ a[j] ^ (w[lcp(i,j)]); minn = min(minn,val); } ans += minn; } cout << ans; return 0; } cout << "0\n"; return 0; } ```