模拟赛题目选集

A. 【线上训练13】二叉树

题解:贪心,先求出这两个序列,i在第一个序列位置为\(a[i]\),第二个为\(b[i]\),如果\(a[i] > b[i]\), \(ans += 2^{a[i]-b[i]}\)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 7;
const int mo = 998244353;
int n,tot,cnt;
int l[maxn],r[maxn],p[maxn],q[maxn],tmp[maxn];
int vis[maxn],ans1[maxn],ans2[maxn];
ll ans;
void dfs1(int x){
    p[++cnt] = x;
    if(l[x])
    dfs1(l[x]);
    if(r[x])
    dfs1(r[x]);
}
void dfs2(int x){
    if(l[x])
    dfs2(l[x]);
    if(r[x])
    dfs2(r[x]);
    q[++tot] = x;
}
ll qp(ll a,ll b){
    ll ans = 1,base = a;
    while(b != 0){
        if(b & 1)ans = ans * base % mo;
        base = base * base % mo;
        b >>= 1;
    }
    return ans;
}
int main()
{
    scanf("%d",&n);
    for(int i = 1;i <= n;i ++){
        scanf("%d%d",&l[i],&r[i]);
    }
    dfs1(1);
    dfs2(1);
    for(int i = 1;i <= n;i ++){
        if(vis[p[i]] == 0 && vis[q[i]] == 0){
            vis[p[i]] = vis[q[i]] = 1;
            if(p[i] != q[i])tmp[p[i]] = 1,tmp[q[i]] = 0;
            else tmp[p[i]] = tmp[q[i]] = 0;
            ans1[i] = 1;
            ans2[i] = 0;
        }
        if(vis[p[i]] && vis[q[i]]){
            ans1[i] = tmp[p[i]];
            ans2[i] = tmp[q[i]];
        }
        if(vis[p[i]] && vis[q[i]] == 0){
            ans1[i] = tmp[p[i]];
            ans2[i] = 0;
            tmp[q[i]] = 0;vis[q[i]] = 1;
        }
        if(vis[p[i]] == 0 && vis[q[i]]){
            ans2[i] = tmp[q[i]];
            ans1[i] = 1;
            tmp[p[i]] = 1;vis[p[i]] = 1;
        }
    }
    ll sum1 = 0,sum2 = 0;
    for(int i = 1;i <= n;i ++){
        if(ans1[i])sum1 = (sum1 + ans1[i] * qp(2,n - i)) %mo;
        if(ans2[i])sum2 = (sum2 + ans2[i] * qp(2,n - i)) %mo;
    }
    ans = (sum1 - sum2 + mo)%mo;
    cout<<ans<<endl;
    return 0;
}

B. 【线上训练13】子序列

题解:子序列\(dp\),\(dp[i]\)表示到i的子序列个数,转移见代码。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;
const int maxn = 5010100;
const int mo = 998244353;
int m,len;
char c[maxn],s[maxn<<1],tmp[maxn];
int last[30];
ll dp[maxn];
ll ans;
int main()
{
    scanf("%d",&m);
    scanf("%s",c + 1);
    len = 0;
    for(int i = 1;i <= m;i ++){
        len *= 2;
        for(int j = 1;j <= len;j ++){
            if(j % 2 == 1)tmp[j] = c[i];
            else tmp[j] = s[j/2];
        }
        tmp[++len] = c[i];
        for(int j = 1;j <= len;j ++)s[j] = tmp[j];
    }
    for(int i = 1;i <= len;i ++){
        if(last[s[i] - 'a'] == 0)dp[i] = (dp[i-1]*2% mo + 1)% mo;
        else dp[i] = (dp[i - 1] * 2 % mo - dp[last[s[i] - 'a'] - 1] + mo) %mo;
        last[s[i] - 'a'] = i;
    //  cout<<dp[i]<<endl;
    }
    cout<<dp[len]<<endl;
    return 0;
}

T1.answer

暴力map哈希,记得特判\(p = 0,q = 0\)的情况。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;
string s[100101];
int flag[1010];
int n,m,p,q;
map <string,int> mp;
void dfs(int x){
    if(x == m + 1){
        string tmp,tmp1;
        tmp.clear();
        for(int i = 1;i <= m ;i ++)
        if(flag[i] == 1)tmp += 'Y';else tmp += 'N';
        for(int i = 1;i <= m ;i ++)
        if(flag[i] == 1)tmp1 += 'N';else tmp1 += 'Y';
        if(mp[tmp] == 0 && mp[tmp1] == 0){
            cout<<tmp<<endl;exit(0);
        }
        return ;
    }
    flag[x] = 0;
    dfs(x + 1);
    flag[x] = 1;
    dfs(x + 1);
}
int main()
{
    //freopen("answer.in","r",stdin);
    //freopen("answer.out","w",stdout);
    scanf("%d%d%d%d",&n,&m,&p,&q);
    for(int i = 1;i <= n ;i ++){
        cin >> s[i];
        mp[s[i]] ++;
    }
    sort(s + 1,s + 1 + n);
    if(p == 0 && q == 0){
        dfs(1);
    }
    if(p == 0 && q){
        for(int i = n ;i >= 1; i--){
            string tmp;
            for(int j = 0;j < m;j ++)
            if(s[i][j] == 'N')tmp += 'Y';else tmp += 'N';
            if(mp[s[i]] == q){
                cout<<tmp<<endl;
                return 0;
            }
        }
    }
    for(int i = 1;i <= n ;i ++){
        string tmp;
        for(int j = 0;j < m;j ++)
        if(s[i][j] == 'N')tmp += 'Y';else tmp += 'N';
        if(mp[s[i]] == p && mp[tmp] == q){
            cout<<s[i]<<endl;
            return 0;
        }
    }
    cout<<-1<<endl;
    return 0;
}

T2.seq

类似背包的\(dp\),单独考虑异或和与两种操作,由于与运算没有逆运算,所以刷表\(dp\)转移.

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const ll mo = 1e9 + 7;
ll dp1[1010][2021],dp2[1010][2020],ans;
int a[1010];
int n;
ll mul(ll a,ll b){
    ll ans = 0,base = a;
    while(b != 0){
        if(b & 1)ans = (ans + base) % mo;
        base = (base + base)% mo;
        b >>= 1;
    }
    return ans;
}
int main()
{
    scanf("%d",&n);
    for(int i = 1;i <= n ;i ++){
        scanf("%d",&a[i]);
        dp2[i][a[i]] = 1;
        dp1[i][a[i]] = 1;
    }
    for(int i = 2;i <= n ;i ++){
        for(int j = 0;j <= 1025;j ++){
            dp1[i][j^a[i]] = (dp1[i][j^a[i]]%mo + dp1[i-1][j]%mo)%mo;
            dp1[i][j] = (dp1[i][j]%mo + dp1[i-1][j]%mo)%mo;
        }
    }
    for(int i = n - 1;i >= 1; i --){
        for(int j = 0;j <= 1025;j ++){
            dp2[i][j&a[i]] = (dp2[i][j&a[i]]%mo + dp2[i+1][j]%mo)%mo;
            dp2[i][j] = (dp2[i][j]%mo + dp2[i+1][j]%mo)%mo;
        }
    }
    for(int i = 1;i <= n ;i ++){
        for(int s = 0;s <= 1024;s ++){
            ans = (ans + (dp1[i][s] - dp1[i-1][s] + mo)%mo*dp2[i + 1][s]%mo)%mo;
        }
    }
    cout<<ans<<endl;
    return 0;
}

T3.travel

一道有趣的最短路
当T很大的时候,我们考虑 如果\(0 -> x -> n - 1\)路径时间为T,且 从x出发有一个时间为d的环,则 一定存在一个K满足 \(K + p * d = T\)(至少T满足条件),这样我们就能绕着环走p次就能构成一条时间为T的路径。
显然要求的路径一定经过 0,而且在合法情况下从0号点出发一定存在一条边,否则0号点和n - 1号就是不联通的。随便取一条边时间为d, 则能构成从0号点出发的一个时间为2d的环。
这样原题就化为最短路问题了,dis[i][j] 代表到达i号点,时间为 \(j + p * 2d\),最小的 \(j+p * 2d\)
最后判断dis[n -1][T % 2d] 是否小于等于T即可。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 210;
int n,m,T;
ll t,d,dis[maxn][20001],q2[maxn*10000];
int l,pre[maxn],last[maxn],other[maxn],len[maxn];
int q1[maxn*10000],vis[maxn][20010];
void add(int x,int y,int z){
    l ++;
    pre[l] = last[x];
    last[x] = l;
    other[l] = y;
    len[l] = z;
}
void spfa(){
    memset(dis,0x3f,sizeof dis);
    memset(q1,0,sizeof q1);
    memset(q2,0,sizeof q2);
    dis[0][0] = 0;
    q1[1] = 0,q2[1] = 0;
    int h = 0,t = 1;
    while(h != t){
        h ++;
        int u = q1[h],f = q2[h];
        vis[u][f] = 0;
        for(int p = last[u];p; p = pre[p]){
            int v = other[p];
            if(dis[v][(f + len[p])%d] > dis[u][f] + len[p]){
                dis[v][(f + len[p])%d] = dis[u][f] + len[p];
                if(vis[v][(f + len[p]) % d])continue;
                vis[v][(f + len[p])%d] = 1;
                t ++;
                q1[t] = v;q2[t] = (f + len[p])%d;
            }
        }
    }
}
int main()
{
    scanf("%d",&T);
    while(T --){
        scanf("%d%d%lld",&n,&m,&t);
        l = 0;
        memset(last,0,sizeof last);
        for(int i = 1;i <= m ;i ++){
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c);add(b,a,c);
        }
        d = 1e9 + 7;
        if(last[0] == 0){
            cout<<"Impossible"<<endl;continue;
        }
        for(int p = last[0];p;p = pre[p]){
            d = min(d,(ll)len[p]);
        }
        d *= 2;
        spfa();
        if(dis[n - 1][(int)(t % d)] <= t){
            cout<<"Possible"<<endl;
        }
        else cout<<"Impossible"<<endl;
    }
    return 0;
}

改造二叉树

中序遍历 + LIS

数字对

st表 + 二分

T1-A. 【线上训练 8】闯关

题解:一道贪心,由于题目保证存在合法解,所以难度肯定是呈段递增的,又由于保证挑选关卡时编号小的在前,所以直接分段贪心即可。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
int ans[101001],cnt = 1;
int vis[101000],f[101010];
int p[101010];
int n;
int main()
{
    scanf("%d",&n);
    for(int i = 1;i <= n ;i ++){
        scanf("%d",&p[i]);
    }
    for(int i = 1;i <= n;i ++){
        f[p[i]] = cnt;
        if(p[i] > p[i + 1] && i != n)cnt ++;
    }
    for(int i = 1;i <= n ;i ++){
        if(vis[f[i]] == 0){
            ans[i] = f[i];
            vis[f[i]] = 1;
        }
        else ans[i] = ++cnt;
    }
    for(int i = 1;i <= n ;i ++)
    cout<<ans[i]<<" ";
    return 0;
}

T2-B. 【线上训练 8】动物园

题解:一道非常好的带权并查集,可以发现答案的总方案是固定的,所以可以将方案数转化为概率,由于主场优势,所以主场有\(\frac{2}{3}\)的胜率,客场有\(\frac{1}{3}\)的胜率,对于并查集,我们也可以像线段树一样打懒标记,来维护答案对于根节点的标记要路径压缩完后再下放,避免重复

T3-C. 【线上训练 8】树

30pts做法:暴力建树,跑树形dp.

60pts做法:对于\(T_{i-1}\)我们不需要建出树来,可以直接把上回树形dp的结果直接附过来,再跑树形dp即可。

100pts做法:咕咕咕咕

60pts代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int mo = 998244353;
const int maxn = 1e5 + 7;
int n,m;
int tmp1,tmp2;
int dp[maxn][2],fr;
int l,last[maxn],other[maxn<<1],pre[maxn<<1];
void add(int x,int y){
    l ++;
    pre[l] = last[x];
    last[x] = l;
    other[l] = y;
}
void dfs(int x,int fa){
    dp[x][1] = (tmp2 + 1)%mo;dp[x][0] = max(tmp1,tmp2);
    for(int p = last[x];p;p = pre[p]){
        int v = other[p];
        if(v == fa)continue;
    //  cout<<x<<" "<<v<<endl;
        dfs(v,x);
        dp[x][0] = (dp[x][0] + max(dp[v][0],dp[v][1])%mo)%mo;
        dp[x][1] = (dp[x][1] + dp[v][0])%mo;
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 1;i < n;i ++){
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);add(v,u);
    }
    dfs(1,0);
    cout<<max(dp[1][0] ,dp[1][1])<<endl;
    fr = 1;
    for(int i = 1;i <= m ;i ++){
        int k;
        scanf("%d",&k);
        tmp1 = dp[fr][1];
        tmp2 = dp[fr][0];
        memset(dp,0,sizeof dp);
        dfs(k,0);
        cout<<max(dp[k][0],dp[k][1])%mo<<endl;
        fr = k;
    }
    return 0;
}

T1路径

题解:如图,树本质上就可以分解成这样的若干条链,若只在链上长度小于等于2就可以,在树上的话就需要小于等于3,题目也正满足条件,所以遇到深度为奇数直接输出,否则回溯时在输出。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 7;
int n;
int l,pre[maxn<<1],last[maxn],other[maxn<<1];
int dep[maxn];
void add(int x,int y){
    l ++;
    pre[l] = last[x];
    last[x] = l;
    other[l] = y;
}
void dfs(int x,int fa){
    if(dep[x] % 2 == 1)cout<<x<<" ";
    for(int p = last[x];p;p = pre[p]){
        int v = other[p];
        if(v == fa)continue;
        dep[v] = dep[x] + 1;
        dfs(v,x);
    }
    if(dep[x] % 2 == 0)cout<<x<<" ";
}
int main()
{
    scanf("%d",&n);
    for(int i = 1;i < n ;i ++){
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);add(b,a);
    }
    cout<<"Yes"<<endl;
    dep[1] = 1;
    dfs(1,0);
    return 0;
}

T2魔法

(我好菜,基本\(dp\)都想不出)

题解:首先kmp预处理每个子串,考虑dp,设计dp[i]表示i这个位置去掉的后的最小代价,直接转移的话就直接枚举上一次的位置,保证i,j之间没有子串,可以通过kmp处理出每个位置的最小左界,复杂度\(O(n^{2})\)。通过线段树再次优化变为\(O(nlogn)\)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 7;
int n,m;
char s[11][maxn],T[maxn];
int cnt,l[maxn];
int a[maxn],dp[maxn];
int nxt[maxn];
struct node{
    int l,r,mn;
}t[maxn*4];
void kmp(int len,int j){
    int p = 0;
    memset(nxt,0,sizeof nxt);
    for(int i = 2;i <= len;i ++){
        while(p && s[j][i] != s[j][p + 1])p = nxt[p];
        if(s[j][i] == s[j][p + 1])p ++;
        nxt[i] = p;
    }
    p = 0;
    for(int i = 1;i <= n;i ++){
        while(p && T[i] != s[j][p + 1]) p = nxt[p];
        if(s[j][p + 1] == T[i]){
            p ++;
            if(p == len){
                l[i] = max(l[i],i - len + 1);
                p = nxt[p];
            }
        }
    }
}
void build(int x,int l,int r){
    t[x].l = l;t[x].r = r;
    if(l == r){
        return ;
    }
    int mid = l + r >> 1;
    build(x+x,l,mid);build(x+x+1,mid + 1,r);
    t[x].mn = min(t[x+x].mn,t[x+x+1].mn);
}
void change(int x,int y,int z){
    if(t[x].l == y && t[x].r == y){
        t[x].mn = z;
        return ;
    }
    int mid = t[x].l + t[x].r >> 1;
    if(y <= mid)change(x+x,y,z);
    else change(x+x+1,y,z);
    t[x].mn = min(t[x+x].mn,t[x+x+1].mn);
}
int ask(int x,int l,int r){
    if(t[x].l == l && t[x].r == r){
        return t[x].mn;
    }
    int mid = t[x].l + t[x].r >> 1;
    if(r <= mid)return ask(x+x,l,r);
    else if(l > mid)return ask(x+x+1,l,r);
    else {
        return min(ask(x+x,l,mid),ask(x+x+1,mid+1,r));
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    scanf("%s",T + 1);
    for(int i = 1;i <= n ;i ++){
        scanf("%d",&a[i]);
    }
    for(int i = 1;i <= m;i ++){
        scanf("%s",s[i] + 1);
    }
    for(int i = 1;i <= m ;i ++){
        int k = strlen(s[i] + 1);
        kmp(k,i);
    }
    int ans = 1e9 + 7;
    memset(dp,0x3f,sizeof dp);
    dp[0] = 0;
    int p = 0;
    build(1,1,n);
    for(int i = 1;i <= n + 1;i ++){
        p = max(p,l[i-1]);
        int tmp = 0;
        if(p != 0)tmp = ask(1,p,i - 1);
        else tmp = 0;
        dp[i] = tmp + a[i];
        if(i != n + 1)change(1,i,dp[i]);
    }
    cout<<dp[n+1]<<endl;
    return 0;
}

T2灵魂画师(paint)

期望DP,(我只会写暴力啊,637巨佬直接A了啊)。
\(dp\)考虑一个点被修改了\(i\)次,颜色为\(j\)的概率
\(dp[i][j*p]\) += \(dp[i-1][j]/2 * c\)
最后把概率乘上颜色,就是答案。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read(){
    int x;scanf("%d",&x);return x;
}
int n,c,k;
int l,r;
double ans;
double dp[101][101];
int cnt[101],mx;
int main()
{
    n = read(),c = read(),k = read();
    for(int i = 1;i <= k;i ++){
        int l = read(),r = read();
        for(int j = l;j <= r;j ++)
        cnt[j] ++,mx = max(mx,cnt[j]);
    }
    dp[0][1] = 1;
    for(int i = 0;i <= mx;i ++){
        for(int j = 0;j < c;j ++){
            for(int p = 0;p < c;p ++){
                dp[i+1][j*p%c] += dp[i][j]/(2.0*c);
            }
            dp[i+1][j] += dp[i][j]/2.0;
        }
    }
    for(int i = 1;i <= n ;i ++){
        for(int j = 0;j < c;j ++){
            ans += dp[cnt[i]][j] * (double)j;
        }
    }
    printf("%.3lf",ans);
    return 0;
}

T1星空(star)

仔细观察,可以发现一个点是否必胜,取决于从它下方转移过来的三个点。这样既可dp,\(dp[i][j] = 1(dp[i-1][j] = 1 || dp[i][j-1] = 1||dp[i-1][j-1])\)
这样dp后打表,也可以发现规律,只有横纵坐标均为奇数才必输。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
int read(){
    int x;scanf("%d",&x);return x;
}
int n,m;
int dp[1010][1010];
int main()
{
    for(int i = 2;i <= 1001;i += 2)
    dp[1][i] = dp[i][1] = 1;
    //dp[2][3] = 1;dp[3][2] = 1;dp[2][2] = 1;
    for(int i = 2;i <= 1001 ;i ++){
        for(int j = 2;j <= 1001 ;j ++){
            if(dp[i-1][j] == 0||dp[i][j-1] == 0||dp[i-1][j-1] == 0)
            dp[i][j] = 1;
        }
    }
    while(1){
        n = read(),m = read();
        if(n == 0)return 0;
        if(dp[n][m])cout<<"Yuri"<<endl;
        else cout<<"Chito"<<endl;
    }
    return 0;
}

T2洗衣(cloth)

这里满分做法先咕掉了,先介绍70pts做法
先把树建出来,对于答案,考虑每条边的贡献,\(ans += len[p] * siz[v] * (all - siz[v])\);

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
int read(){
    int x;scanf("%d",&x);return x;
}
const int maxn = 1e5 + 7;
const int mo = 1e9 + 7;
int l,pre[61][maxn<<1],last[51][maxn];
struct node{
    int x,y,z;
}e[61][maxn<<1];
int m;
ll ans;
int siz[maxn],cnt[61],sum[61];
void add(int p,int x,int y,int z){
    l ++;
    pre[p][l] = last[p][x];
    last[p][x] = l; 
    e[p][l].x = x;
    e[p][l].y = y;e[p][l].z = z;
}
void dfs(int q,int x,int fa){
    siz[x] = 1;
    for(int p = last[q][x];p;p = pre[q][p]){
        int v = e[q][p].y;
        if(v == fa)continue;
        dfs(q,v,x);
        siz[x] += siz[v];
        ans = (ans + 1ll*e[q][p].z%mo*1ll*siz[v]%mo*1ll*(sum[q] - siz[v])%mo)%mo;
    }
}
int main()
{
    m = read();
    sum[0] = 1;
    for(int i = 1;i <= m ;i ++){
        int a = read(),b = read(),c = read(),d = read(),w = read();
        cnt[i] = cnt[a] + cnt[b] + 2;
        sum[i] = sum[a] + sum[b];
        l = 0;ans = 0;
        for(int j = 1;j <= cnt[a];j ++)
        add(i,e[a][j].x,e[a][j].y,e[a][j].z);
        for(int j = 1;j <= cnt[b];j ++)
        add(i,e[b][j].x + sum[a],e[b][j].y + sum[a],e[b][j].z);
        add(i,c,d + sum[a],w);add(i,d+sum[a],c,w);
        memset(siz,0,sizeof siz);
        dfs(i,0,-1);
        printf("%lld\n",ans%mo);
    }
    return 0;
}

T3无题(noname)

这题有好多部分分啊,会了线段树就有50pts,会了主席树就有了70pts
正解:由于$k <= 10 $,用线段树维护前十大,合并上传时,就类似于归并一样的操作。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
int read(){
    int x;scanf("%d",&x);return x;
}
const int maxn = 1e5 + 7;
struct ten{
    int w[12];
};
struct node{
    int l,r,lazy,mx;
    ten in;
}t[4*maxn];
int cnt,n,q;
int a[maxn];
void update(int x){
    int p1 = 1,p2 = 1;
    for(int i = 1;i <= 10;i ++){
        if(t[x+x].in.w[p1] > t[x+x+1].in.w[p2]){
            t[x].in.w[i] = t[x+x].in.w[p1];
            p1 ++;
        }
        else {
            t[x].in.w[i] = t[x+x+1].in.w[p2];
            p2 ++;
        }
    //  cout<<t[x].in.w[i]<<endl;
    }
}
void build(int x,int l,int r){
    t[x].l = l;t[x].r = r;
    if(l == r){
        t[x].in.w[1] = a[l];
        return ;
    }
    int mid = l + r >> 1;
    build(x+x,l,mid);build(x+1+x,mid+1,r);
    update(x);
}
void motify(int x){
    for(int i = 1;i <= 10;i ++)
    if(t[x+x].in.w[i])t[x+x].in.w[i] += t[x].lazy;
    for(int i = 1;i <= 10;i ++)
    if(t[x+x+1].in.w[i])t[x+x+1].in.w[i] += t[x].lazy;
    t[x+x].lazy += t[x].lazy;
    t[x+x+1].lazy += t[x].lazy;
    t[x].lazy = 0;
}
void change(int x,int l,int r,int z){
    if(t[x].l == l && t[x].r == r){
        for(int i = 1;i <= 10;i ++){
            if(t[x].in.w[i])
            t[x].in.w[i] += z;
            else break;
        }
        t[x].lazy += z;
        return ;
    }
    if(t[x].lazy)motify(x);
    int mid = t[x].l + t[x].r >> 1;
    if(r <= mid)change(x+x,l,r,z);
    else if(l > mid)change(x+x+1,l,r,z);
    else{
        change(x+x,l,mid,z);change(x+x+1,mid+1,r,z);
    }
    update(x);
}
ten ask(int x,int l,int r){
    if(t[x].l == l && t[x].r == r){
        return t[x].in;
    }
    if(t[x].lazy)motify(x);
    int mid = t[x].l + t[x].r >> 1;
    if(r <= mid)return ask(x+x,l,r);
    else if(l > mid)return ask(x+x+1,l,r);
    else {
        int p1 = 1,p2 = 1;
        ten tmp1 = ask(x+x,l,mid),tmp2 = ask(x+x+1,mid+1,r),ans;
        for(int i = 1;i <= 10;i ++){
            if(tmp1.w[p1] > tmp2.w[p2]){
                ans.w[i] = tmp1.w[p1];
                p1 ++;
            }
            else {
                ans.w[i] = tmp2.w[p2];
                p2 ++;
            }
            //cout<<ans.w[i]<<endl;
        }
        return ans;
    }
}
int main()
{
    scanf("%d%d",&n,&q);
    for(int i = 1;i <= n ;i ++)
    scanf("%d",a+i);
    build(1,1,n);
    for(int i = 1;i <= q;i ++){
        int opt = read(),l = read(),r = read(),x = read();
        if(opt == 1){
            change(1,l,r,x);
        }
        else {
            if(x > r - l + 1){cout<<-1<<endl;continue;}
            ten ans = ask(1,l,r);
            printf("%d\n",ans.w[x]);
        }
    }
    return 0;
}

T2区间(range)

菜鸡的我只会写暴力,咕咕咕。
正解:and运算是单调非增,or运算是单调非减,利用st表一样的想法,查询区间值,由于多and,或者or相同的数,并不影响结果,所以是正确的。利用二分的思想差找符合答案的区间,记录答案即可。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 7;
const int mo = 1e9 + 7;
int ast[maxn][21],hst[maxn][21];
int n,a,b,c,d;
int s[maxn],ans;
int cha1(int l,int r){
    int j = log(r - l + 1)/log(2);
    return ast[l][j] & ast[r - (1 << j) + 1][j];
}
int cha2(int l,int r){
    int j = log(r - l + 1)/log(2);
    return hst[l][j] | hst[r - (1 << j) + 1][j];
}
void pre(){
    for(int j = 1;j <= 19;j ++){
        for(int i = 1;i <= n &&i + (1 << j) - 1<= n;i ++){
            ast[i][j] = ast[i][j-1]&ast[i + (1 << (j-1))][j-1];
            hst[i][j] = hst[i][j-1]|hst[i + (1 << (j-1))][j-1];
        }
    }
}
int main()
{
    scanf("%d%d%d%d%d",&n,&a,&b,&c,&d);
    for(int i = 1;i <= n ;i ++)
    scanf("%d",&s[i]),ast[i][0] = s[i],hst[i][0] = s[i];
    pre();
    for(int i = 1;i <= n ;i ++){
        int l = i,r = n,cnt1 = 0,cnt2 = 0,cnt3 = 0,cnt4 = 0;
        while(l <= r){
            int mid = l + r >> 1;
            int x = cha1(i,mid);
            if(x >= a)cnt2 = mid,l = mid + 1;
            else r = mid - 1;
        }
        l = i,r = n;
        while(l <= r){
            int mid = l + r >> 1;
            int x = cha1(i,mid);
            if(x > b)l = mid + 1;
            else r = mid - 1,cnt1 = mid;
        }
        l = i,r = n;
        while(l <= r){
            int mid = l + r >> 1;
            int x = cha2(i,mid);
            if(x >= c)cnt3 = mid,r = mid - 1;
            else l = mid + 1;
        }
        l = i,r = n;
        while(l <= r){
            int mid = l + r >> 1;
            int x = cha2(i,mid);
            if(x > d)r = mid - 1;
            else l = mid + 1,cnt4 = mid;
        }
        if(cnt1&&cnt2&&cnt4&&cnt3){
            int L = max(cnt1,cnt3);
            int R = min(cnt2,cnt4);
            if(R >= L)ans += R - L + 1,ans %= mo;
        }
    }
    printf("%d",ans);
    return 0;
}

T3 收集果子(fruit)

树形DP,一眼树上背包,然而不会转移(咕咕咕,我好菜)
设计状态\(dp[i][j]\),\(i\)的子树中选了\(j\)个果子。
\(dp[x][i+j] += dp[x][i] * dp[v][j]\);
如果\(dp[x][j](j == 0)\)
\(dp[x][0] = dp[x][0] * pow(2,siz[x]-1)\);

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read()
{
    char ch=getchar();int x=0,f=1;
    while(ch<'0' || ch>'9') {
       if(ch=='-') f=-1;
          ch=getchar();
    }
    while(ch<='9' && ch>='0') {
       x=x*10+ch-'0';
       ch=getchar();
    }
    return x*f;
}
inline ll readl()
{
    char ch=getchar();ll x=0,f=1;
    while(ch<'0' || ch>'9') {
       if(ch=='-') f=-1;
          ch=getchar();
    }
    while(ch<='9' && ch>='0') {
       x=x*10+ch-'0';
       ch=getchar();
    }
    return x*f;
}
const int mo = 1e9 + 7;
const int maxn = 2e3 + 7;
int l,pre[maxn<<1],last[maxn],other[maxn<<1];
int n,k,w[maxn];
ll dp[maxn][maxn],siz[maxn],sum[maxn];
void add(int x,int y){
    l ++;
    pre[l] = last[x];
    last[x] = l;
    other[l] = y;
}
ll qp(int a,int b){
    ll ans = 1,base = a;
    while(b != 0){
        if(b & 1)ans = ans * base % mo;
        base = base * base %mo;
        b >>= 1;
    }
    return ans;
}
void dfs(int x,int fa){
    sum[x] = w[x];dp[x][w[x]] = 1,siz[x] = 1;
    for(int p = last[x];p;p = pre[p]){
        int v = other[p];
        if(v == fa)continue;
        dfs(v,x);
        dp[v][0] = (dp[v][0] + qp(2,siz[v] - 1))%mo;
        for(int i = sum[x];i >= 0;i --){
            if(dp[x][i] == 0)continue;
            for(int j = sum[v];j >= 1; j--){
                dp[x][i+j] = (dp[x][i+j] + 1ll*dp[x][i]*dp[v][j]%mo)%mo;
            }
            dp[x][i] = (1ll*dp[x][i] * dp[v][0])%mo;
            //cout<<dp[x][i]<<" "<<x<<" "<<i<<endl;
        }
        siz[x] += siz[v];
        sum[x] += sum[v];
    }
}
int main()
{
    n = read(),k = read();
    for(int i = 1;i <= n ;i ++)w[i] = read();
    for(int i = 1;i < n ;i ++){
        int a = read(),b = read();
        add(a,b);add(b,a);
    }
    
    dfs(1,0);
    cout<<dp[1][k]<<endl;
    return 0;
}

T1数列(seq)

(脑子被挤了啊)

题解:

每次两个数互相减,其实就相当于更相减损,于是就可以利用辗转相除。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
#include<map>
using namespace std;
typedef long long ll;
ll a1,a2;
ll ans;
void work(ll a,ll b){
    if(b == 0)return ;
    //cout<<a<<" "<<b<<endl;
    ans += a/b;
    work(b,a % b);
}
int main()
{
    scanf("%lld%lld",&a1,&a2);
    if(a1 < a2)swap(a1,a2);
    work(a1,a2);
    cout<<ans + 1<<endl;
    return 0;
}

T2曹将军的战士

一道原题,luogu三元上升子序列
枚举中间节点,利用树状数组查询左右两侧符合要求的数,利用乘法原理再计算答案。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 7;
int n;
int a[maxn],l[maxn],r[maxn],c[maxn];
ll ans;
void add(int x,int y){
    for(;x <= maxn;x += x&(-x))c[x] += y;
}
int ask(int x){
    int tot = 0;
    for(;x > 0;x -= x&(-x))tot += c[x];
    return tot;
}
int main()
{
    scanf("%d",&n);
    for(int i = 1;i <= n ;i ++){
        scanf("%d",&a[i]);
    }
    for(int i = 1;i <= n ;i ++){
    //  cout<<a[i]<<endl;
        l[i] = ask(maxn) - ask(a[i]);
        add(a[i],1);
    }
    memset(c,0,sizeof c);
    for(int i = n;i >= 1; i--){
        r[i] = ask(a[i]-1);
        add(a[i],1);
    }
    for(int i = 1;i <= n ;i ++){
        ans += (ll)l[i] * (ll)r[i];
    }
    cout<<ans<<endl;
    return 0;
}

T3 车辆销售(car)

考试时一眼就觉得这题和NOI2018归程很像,想了想以为应该不会是kruscal重构树,就随便糊了个暴力,结果它真就是个重构树。(猝死)
重构树性质是很多的,这道题就很简单了

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
int read(){
    int x;scanf("%d",&x);return x;
}
typedef long long ll;
const int maxn = 2e5 + 7;
int n,m;
int l,pre[maxn<<1],last[maxn<<1],other[maxn<<1];
int siz[maxn<<1],val[maxn<<1],fa[maxn<<1];
ll ans[maxn];
struct node{
    int a,b,c;
}p[maxn*3];
bool cmp(node a,node b){
    return a.c > b.c;
}
int get(int x){
    if(x == fa[x])return x;
    return fa[x] = get(fa[x]);
}
void add(int x,int y){
    l ++;
    pre[l] = last[x];
    last[x] = l;
    other[l] = y;
}
void dfs(int x,int fa,ll sum){
    for(int p = last[x];p;p = pre[p]){
        int v = other[p];
        ll tmp = 0;
        if(val[v] == val[x])siz[v] = siz[x];
        else tmp += (ll)(siz[x] - siz[v])*(siz[x] - siz[v]);
        ans[v] = sum + tmp;
        dfs(v,x,sum + tmp);
    }
}
int main()
{
    n = read(),m = read();
    for(int i = 1;i <= m;i ++){
        p[i].a = read(),p[i].b = read(),p[i].c = read();
    }
    sort(p + 1,p + 1 + m,cmp);
    int cnt = n;
    for(int i = 1; i <= n ;i ++){
        fa[i] = i;siz[i] = 1;
    }
    for(int i = 1;i <= m ;i ++){
        int fx = get(p[i].a),fy = get(p[i].b);
        if(fx == fy)continue;
        cnt ++;
        add(cnt,fx);
        add(cnt,fy);
        fa[fx] = cnt;
        fa[fy] = cnt;
        fa[cnt] = cnt;
        siz[cnt] = siz[fx] + siz[fy];
        val[cnt] = p[i].c;
    }
    dfs(cnt,0,0);
    for(int i = 1;i <= n ;i ++)cout<<ans[i]<<" ";
    return 0;
}

CometOJ Day1

T1计算机 (computer)

比较简单,主要就是看出变成3会很优(众所周知,oier大胆猜想无须证明)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read(){
    int x;scanf("%d",&x);return x;
}
inline ll readl(){
    ll x;scanf("%lld",&x);return x;
}
const int mo = 1e9 + 7;
ll n,ans;
ll qp(ll a,ll b){
    ll ans = 1,base = a;
    while(b != 0){
        if(b & 1)ans = ans * base % mo;
        base = base * base%mo;
        b >>= 1;
    }
    return ans;
}
int main()
{
    n = readl();
    if(n == 1){
        cout<<1<<endl;return 0;
    }
    if(n == 2){
        cout<<2<<endl;return 0;
    }
    if(n == 4){
        cout<<4;return 0;
    }
    if(n % 3 == 0){
        ans = qp(3,n/3);
        cout<<ans<<endl;
        return 0;
    }
    if(n % 3 == 2){
        ans = qp(3,n/3) * 2 % mo;
        cout<<ans<<endl;
        return 0;
    }
    if(n % 3 == 1){
        ans = qp(3,n/3 - 1) * 4 % mo;
        cout<<ans<<endl;
    }
    return 0;
}

T2探险 (adventure)

恶心模拟题,一堆特判,主要考虑形如“.#.”,“.# * ”而自己只有一个或0个时,可以先放这里走过去再等之后又有‘* ’再回来捡。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read(){
    int x;scanf("%d",&x);return x;
}
inline ll readl(){
    ll x;scanf("%lld",&x);return x;
}
const int maxn = 4e6 + 7;
int T;
char s[maxn];
int main()
{
    T = read();
    while(T --){
        int ans = 0,sum = 0,tmp = 0,cur = 0;
        scanf("%s",s + 1);
        int len = strlen(s + 1);
        for(int i = 1;i <= len;i ++){
            if(s[i] == '#')
            {
                if(i == len)break;
                if(s[i+1] == '#' && s[i+2] == '.')break;
                if(s[i+1] == '#' && s[i+2] == '#')break;
                if(s[i+1] == '#' && sum == 0)break;
                if(sum == 0 && s[i+1] != '*' && s[i-1] != '*')break;
                if(s[i+1] == '.' && sum == 0)break;
                if(s[i+1] == '#' && s[i+2] == '*' && sum == 0)break;
                if(s[i+1] == '#' && s[i+2] == '*' && sum > 0)sum --;
                if(s[i+1] == '.' && sum == 1)sum --,tmp ++;
                if(s[i-1] == '.'&&s[i+1] == '*'&& sum == 0)cur += tmp,tmp = 0;
            }
            else if(s[i] == '*'){
                sum ++;
                if(sum >= 1 && tmp)sum += tmp,tmp = 0;
                if(sum >= 2 && cur)sum += cur ,cur = 0;
            }
        //  cout<<cur<<" "<<sum<<endl;
            ans = max(ans,sum);
        }
        cout<<ans<<endl;
    }
    return 0;
}

T3高速公路 (highway)

先是树形DP求出树上每个子树下的最长链,直径,再对每个节点dp求出去掉子树i之后的直径,类似求每个点的树上最长路径。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read(){
    int x;scanf("%d",&x);return x;
}
inline ll readl(){
    ll x;scanf("%lld",&x);return x;
}
const int maxn = 2e5 + 7;
int n;
int son[maxn];
ll f[maxn],d[maxn];
ll w[maxn],dp[maxn][2],fr[maxn],nxt[maxn],g[maxn],ans,up[maxn],len[maxn<<1];
int l,pre[maxn<<1],last[maxn],other[maxn<<1];
void add(int x,int y,int z){
    l ++ ;
    pre[l] = last[x];
    last[x] = l;
    other[l] = y;
    len[l] = z;
}
void dfs1(int x,int fa)
{
    for(int p = last[x];p;p = pre[p])
    {
        int v = other[p];
        if(v == fa) continue;
        dfs1(v,x);
        f[x] = max(f[x],d[x] + d[v] + len[p]);
        d[x] = max(d[x],d[v] + len[p]);
        f[x] = max(f[x],f[v]);
    //  cout<<f[x]<<d[x]<<endl;
    }
}

void dfs2(int x,int fa){
    int cnt = 0;
    for(int p = last[x];p;p = pre[p]){
        int v = other[p];
        if(v == fa)continue;
        son[++cnt] = v;w[cnt] = len[p];
    }
    fr[0] = 0;nxt[cnt+1] = 0;
    for(int i = 1;i <= cnt; i++){
        fr[i] = max(fr[i-1],d[son[i]] + w[i]);
    }
    for(int i = cnt;i >= 1;i --){
        nxt[i] = max(nxt[i+1],d[son[i]] + w[i]);
    }
    ll mx = -1e13 + 7;
    for(int i = 1;i <= cnt;i ++){
        up[son[i]] = max(up[son[i]],up[x] + w[i]);
        up[son[i]] = max(up[son[i]],max(fr[i-1],nxt[i+1]) + w[i]);
        g[son[i]] = max(g[son[i]],g[x]);
        g[son[i]] = max(g[son[i]],fr[i-1] + nxt[i+1]);
        g[son[i]] = max(g[son[i]],max(fr[i-1],nxt[i+1]) + up[x]);
        g[son[i]] = max(g[son[i]],mx);
        mx = max(mx,f[son[i]]);
    }
    for(int p = last[x];p;p = pre[p]){
        int v = other[p];
        if(v == fa)continue;
        dfs2(v,x);
    }
}
void clear(){
    memset(d,0,sizeof d);
    memset(f,0,sizeof f);
    memset(g,0,sizeof(g));
    memset(dp,0,sizeof(dp));
    memset(up,0,sizeof(up));
    memset(fr,0,sizeof(fr));
    memset(nxt,0,sizeof(nxt));
    memset(son,0,sizeof(son));
    memset(w,0,sizeof(w)); 
}
int main()
{
    n = read();
    for(int i = 1;i < n ;i ++){
        int a = read(),b = read(),c = read();
        add(a,b,c);add(b,a,c);
    }
    dfs1(1,0);dfs2(1,0);
    for(int i = 1;i <= n ;i ++)
    ans = max(ans,(f[i])*g[i]);
    for(int i = 1;i <= l;i ++)len[i] = -len[i];
    clear();
    dfs1(1,0);dfs2(1,0);
    for(int i = 1;i <= n ;i ++)
    ans = max(ans,(f[i])*g[i]);
    cout<<ans<<endl;
    return 0;
}

牛客比赛

T4

一道边界卡死人的DP.
考虑\(dp[i][j][0/1]\)表示到i为止,用了j次手动删除,最后一次用的什么删除
考虑转移
\(dp[i][j][0] = max\{dp[i-1][j][0/1] + b[i]\};\)
\(dp[i][j][1] = max\{dp[i-1][j-1][0] + a[i]\};\)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 7;
int n,m;
ll a[maxn],b[maxn];
ll dp[3][1011][3];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i ++){
        scanf("%lld%lld",&a[i],&b[i]);
    }
    for(int i = 1;i <= n ;i ++){
        dp[i&1][0][1] = dp[(i-1)&1][0][1] + b[i];
        for(int j = 1;j <= min(i-1,m);j ++){
            dp[(i&1)][j][0] = dp[((i-1)&1)][j-1][1] + a[i];//0,shoudong,1,zidong
            dp[(i&1)][j][1] = max(dp[((i-1)&1)][j][0],dp[(i-1)&1][j][1]) + b[i];
        }
    }
    ll ans = -1;
    for(int i = 0;i <= m;i ++){
        ans = max(ans,dp[(n&1)][i][0]);
        ans = max(ans,dp[(n&1)][i][1]);
    }
    printf("%lld",ans);
    return 0;
}

LY的模拟赛

T3网络(net)

最小生成树 + 01背包

T2信仰(sanae)

神仙DP。(到死都觉得是个贪心)
dp[i][j]表示前i天找了j个妖精的最小时间。

for(int i = 1;i <= n ;i ++){
        for(int j = 1;j <= i;j ++){
            int tmp = max(l[i],dp[i-1][j-1]);
            if(tmp + t[i] > r[i])dp[i][j] = dp[i-1][j];
            else dp[i][j] = min(dp[i-1][j],tmp + t[i]);
        }
    }

清北模拟赛

T1

正难则反,暴力搜索不合法的

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read(){
    int x;scanf("%d",&x);return x;
}
inline ll readl(){
    ll x;scanf("%d",&x);return x;
}
int cnt1;
int chose[11];
int l,r,ans,cnt;
void dfs(int x,int y,int len){
    if(y > r)return ;
    if(y >= l && y <= r && x > len){
    //  cout<<y<<endl;
        ans ++;
        return ;
    }
    for(int i = 0;i <= 9;i ++){
        if(!y && i == 0)continue;
        if(chose[i] == i - 1)continue;
        chose[i] ++;
        dfs(x + 1,y*10 + i,len);
        chose[i] --;
    }

}
int main()
{
    //freopen("number1.in","r",stdin);
    //freopen("number1.out","w",stdout);
    l = read(),r = read();
    int r1 = r,l1 = l;
    while(l1 != 0){
        cnt1 ++;
        l1 /= 10;
    }
    while(r1 != 0){
        cnt ++;
        r1 /= 10;
    }
    for(int i = cnt1;i <= cnt;i ++)
    dfs(1,0,i);
    cout<<r - l + 1 - ans<<endl;
    return 0;
}

T2

有趣的树上差分。
先搞出遍历顺序,由于答案加上\(max(size[i]-1,0) * z[i]\),所以对于起点,要在其父节点上++,剩下的就是普通的树上差分

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<set>
#include<vector>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read(){
    int x;scanf("%d",&x);return x;
}
inline ll readl(){
    ll x;scanf("%d",&x);return x;
}
const int maxn = 1e5 + 7;
int dfn[maxn],cf[maxn],f[maxn][20],id[maxn];
int x[maxn],y[maxn],z[maxn],dep[maxn],cnt;
int n,m;
ll ans,mx,sum[maxn];
vector <int> p[maxn];
void dfs(int x,int fa){
    for(int i = 0;i < p[x].size();i ++){
        int v = p[x][i];
        if(v == fa)continue;
        dep[v] = dep[x] + 1;
        f[v][0] = x;
        dfs(v,x);
    }
    dfn[x] = ++cnt;
    id[cnt] = x;
}
void dfs1(int x,int fa){
    sum[x] = cf[x];
    for(int i = 0;i < p[x].size();i ++){
        int v = p[x][i];
        if(v == fa)continue;
        dfs1(v,x);
        sum[x] += sum[v];
        
    }
}
int lca(int u,int v){
    if(dep[u] < dep[v])swap(u,v);
    for(int i = 0;i <= 17;i ++){
        if((dep[u] - dep[v]) & (1 << i))u = f[u][i];
    }
    if(u == v)return u;
    for(int i = 17;i >= 0;i --){
        if(f[u][i] != f[v][i]){
            u = f[u][i];
            v = f[v][i];
        }
    }
    return f[u][0];
}
int main()
{
    freopen("wind.in","r",stdin);
    freopen("wind.out","w",stdout);
    n = read(),m = read();
    for(int i = 1;i < n;i ++){
        int a = read(),b = read();
        p[a].push_back(b);
        p[b].push_back(a);
    }
    for(int i = 1;i <= n ;i ++){
        sort(p[i].begin(),p[i].end());
    }
    dfs(1,0);
    for(int j = 1;j <= 17;j ++){
        for(int i = 1;i <= n;i ++)
        f[i][j] = f[f[i][j-1]][j-1];
    }
    for(int i = 1;i <= m;i ++){
        int x = read(),y = read(),z = read();
        int c = lca(x,y);
        //cout<<x<<" "<<y<<" "<<dfn[x]<<" "<<dfn[y]<<endl;
        if(dfn[x] < dfn[y])cf[f[x][0]] += z,cf[y] += z;
        else cf[f[y][0]] += z,cf[x] += z;
        cf[c] -= z;cf[f[c][0]] -= z;
    }
    dfs1(1,0);
//  for(int i = 1;i <= n;i ++)cout<<sum[i]<<" ";
    for(int i = 1;i <= cnt;i ++){
        ans += sum[id[i]];
        ans -= dep[id[i]];
        mx = max(mx,ans);
    }
    cout<<mx<<endl;
    return 0;
}

T1

简单说一下题面
T组数据,查找l,r区间内满足
1.是一个质数
2.可以被分解为两个质数
\(T<=1e5,l,r<=1e7.\)
这么大的数据范围我们首先想到线性筛,并且在线性筛的基础上,把性质2的数判断出来,到这里这题已经完成一半了。继续思考发现我们要在\(O(1)\)的时间内把答案判断出来,所以我们可以通过前缀和进行优化,来做到\(O(1)\)时间回答。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
bool vis[10000001],flag[10000001];
int prime[10000001],cnt,T,ans[10000001];
void make_prime(int x)
{
    memset(vis,1,sizeof vis);
    vis[1]=0;flag[1]=0;
    for(int i=2;i<=x;i++)
    {
        if(vis[i]==1)
        {
            prime[++cnt]=i;
        }
        for(int j=1;j<=cnt&&i*prime[j]<=x;j++)
        {
            vis[i*prime[j]]=0;
            if(vis[i]==1)flag[i*prime[j]]=1;//性质2
            if(!(i%prime[j]))break;
        }
    }
    return ;
}
int main()
{
    //freopen("prime.in","r",stdin);
    //freopen("prime.out","w",stdout);
    scanf("%d",&T);make_prime(10000000);
    ans[1]=0;ans[2]=1;
    for(int i=3;i<=10000000;i++)
    {
        if(vis[i]==1||flag[i]==1)ans[i]=ans[i-1]+1;
        else ans[i]=ans[i-1];
    }
    while(T--)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        cout<<ans[r]-ans[l-1]<<endl;
    }
    return 0;
}

T2

【题目描述】
密室中有 N 个房间,初始时在1 号房间,而出口在 N 号房间。
密室的每一个房间中可能有着一些钥匙和一些传送门,一个传送门会单向地创造一条
从房间 X 到房间 Y 的通道。
另外,想要通过某个传送门,就必须具备一 些种类的钥匙。
幸运的是,钥匙在打开传送门的封印后,并不会消失。
然而,通过密室的传送门需要耗费大量的时间,因此希望通过尽可能少的传
送门到达出口
另外不能逃出这个密室,如果是这样,请输出“No Solution”。

第一行三个整数 N、M、K,分别表示房间的数量、传送门的数量以及钥匙的种类
数。
接下来 N 行,每行 K 个 0 或 1,若第 i 个数为 1,则表示该房间内有第 i 种钥
匙,若第 i 个数为 0,则表示该房间内没有第 i 种钥匙。
接下来 M 行,每行先读入两个整数 X,Y,表示该传送门是建立在 X 号房间,通向
Y 号房间的,再读入 K 个 0 或 1,若第 i 个数为 1,则表示通过该传送门需 要 i 种钥匙,若第 i 个数为 0,则表示通过该传送门不需要第 i 种钥匙。

这是一到状压bfs
压缩钥匙,并开一维记录,通过位运算对钥匙进行操作。
填坑

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<iostream>
using namespace std;
const int maxn=6005;
int l,pre[maxn],other[maxn],last[maxn],len[maxn];
int que[6000005],n,m,k;
int val[maxn],cur[6000005],ans[maxn][4000];
void add(int x,int y,int z)
{
    l++;
    pre[l]=last[x];
    last[x]=l;
    other[l]=y;
    len[l]=z;
}
int main()
{
    scanf("%d%d%d", &n, &m, &k);
    for(int i = 1; i <= n; i++)
    {
         int cur;
         for(int j = 0; j < k; j++)
         {
              scanf("%d", &cur);
              val[i] += cur << j;
         }
    }
    for(int i = 1; i <= m; i++)
    {
        int x,y,curr,c=0;
        scanf("%d%d", &x, &y);
        for(int j = 0; j < k; j++){
        scanf("%d", &curr);
        c += curr << j;
        }
        add( x, y, c);
    }
    memset(ans, -1, sizeof(ans));
    int h=0,t=1;
    ans[1][val[1]]=0;
    que[1]=1;cur[1]=val[1];
    while(h<=t)
    {
        h++;
        int u = que[h]; 
        for(int p = last[u] ;p; p = pre[p])
        {
            int v = other[p];
            if((cur[h] & len[p]) == len[p] && ans[v][cur[h] | val[v]] == -1)
            {
                t++;
                que[t] = v;
                cur[t] = cur[h]|val[v];
                ans[v][cur[t]] = ans[u][cur[h]]+1;
                if(v == n)
                {
                    cout<<ans[v][cur[t]]<<endl;
                    return 0;
                }
            }
        }
    }
    cout<<"No Solution"<<endl;
    return 0;
}

T3

【题目描述】
城市 X 城是一个含有 N 个节点的无向图, X 城只建造 N – 1 条边,使得城
市的各个地点能够相互到达。你计划蒸发 Q 天的学水,每一天caiyingqi从 A 地走
到 B 地,并在沿 途各个地点留下一个水塘。此后,你会从 C 地走到 B 地,
蒸发沿途的水塘。由于 X 城是一个学%横行的城市,caiyingqi留下的水塘即使没有被蒸发,也会在第二天之前蒸发殆尽。 现在,你想要知道,你每一天能够蒸发多
少水塘呢?

可以看出这是一道lca题,求两条路径有多少公共点呢
暴力想法:树上染色,求公共点,可以用树上差分解决,复杂度为
$ O(q(n+logn)) $ 约为\(O(n * q)\)
显然无法解决2e5的n和q。
正解:我们可以画几组样例发现答案一定是任意两点间lca中深度最深的点,与b间的距离,这里就不加以证明,各位可以自行脑补证明。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=400001;
int pre[maxn],other[maxn],last[maxn],l,n,q,num;
int dep[maxn];
int f[maxn][20];
void add(int x,int y)
{
    l++;
    pre[l]=last[x];
    last[x]=l;
    other[l]=y;
}
void dfs(int u)
{
    for(int p=last[u];p;p=pre[p])
    {
        int v=other[p];
        if(v==f[u][0])continue;
        dep[v]=dep[u]+1;
        f[v][0]=u;
        dfs(v);
    }
}
int  lca(int u,int v)
{
    if(dep[u]<dep[v])swap(u,v);
    for(int i=0;i<=16;i++)
    if((dep[u]-dep[v])&(1<<i))u=f[u][i];
    if(u==v)return u;
    for(int j=17;j>=0;j--)
    {
        if(f[v][j]!=f[u][j])
        {
            u=f[u][j];
            v=f[v][j];
        }
    }
    return f[u][0];
}
int main()
{
    scanf("%d%d%d",&n,&q,&num);
    for(int i=1;i<n;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b);add(b,a);
    }
    dfs(1);
    for(int j=1;j<=17;j++)
    for(int i=1;i<=n;i++)
    f[i][j]=f[f[i][j-1]][j-1];
    for(int i=1;i<=q;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
            int ans=1;
            int x=lca(a,b);int y=lca(b,c);int z=lca(a,c);
            if(dep[x]>=dep[y]&&dep[x]>dep[z])
            ans+=dep[x]+dep[b]-2*dep[lca(x,b)];
            else if(dep[y]>dep[x]&&dep[y]>dep[z])
            ans+=dep[y]+dep[b]-2*dep[lca(y,b)];
            else if(dep[z]>=dep[x]&&dep[z]>=dep[y])
            ans+=dep[z]+dep[b]-2*dep[lca(z,b)];
            cout<<ans<<endl;
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/wtz2333/p/12237309.html