9.15&&9.16模拟赛订正

时间:2019-09-18
本文章向大家介绍9.15&&9.16模拟赛订正,主要包括9.15&&9.16模拟赛订正使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

先订正9.16的比赛 因为打9.15的比赛的时候电脑崩了 然后gg了;

9.16 T1:题目大意:给定一棵树,对于n个节点 每个节点有一个权值为(1或者0),选择一个子连通块,使得这个连通块内0的个数与1的个数差的绝对值最大;

后来听chdy大佬说这是一种套路题目 所以我学习了一下换根和二次扫描dp;

这种题目一般有一种模型:给定一棵树,我们需要以每个点作为整颗树为根 统计答案;常见做法应该是通过两次扫描做出来,由(n^2)的复杂度降到(n);

1.在第一次扫描的时候任意指定一个节点作为根,然后进行一次dfs,从下往上,回溯的时候统计答案;

2.第二次扫描的时候从刚才选出的根出发,进行一次dfs,从上往下的更新换根之后的答案;

https://www.acwing.com/problem/content/289/

题目来自算法竞赛进阶指南对于二次扫描与换根的介绍;

然后对于这个题目 就是一个不定根的树形dp的做法 我们有一个n^2的做法 对于每一个节点都作为根 进行一次dfs 统计最大流量,然后max;考虑(n)的做法;

按照上面的过程 我们不妨选择1号节点先作为整棵树的根,统计出来每个节点i作为其子树的根的时候的最大流量d[x],显然一次dp我们就可以做出来;

然后我们考虑每个节点作为整棵树的根的做法;设f[x]表示x作为整颗树的根的答案,显然我们知道f[1]=d[1];

当f[x]已经求出来,考虑其子节点y,对于F[y]包含两部分:

1.对于以y为根的子树内部的最大流量,即d[y];

2.对于位于y除了他子树内部以外的流量,即沿着x向上走;

针对上面那个题目,当x作为源点,流量为f[x] 我们知道从x流向y的流量应当是min(flow(x,y),d[y]), 所以从x流向y以外的节点的流量就是两者的差;

于是我们把y作为整棵树的根的时候 我们可以先从y流向x,显然f[y]=d[y]+min(f[x]-min(flow(x,y),d[y]),flow(x,y));

f[x]-min(flow(x,y),d[y]) 指的是除了x对于y的那个分支以外,对于其他点最大流量;

我其实第一遍并没有考虑到是否是叶子的情况,但是这样的做法在是一条链或者只有两个,三个点的时候就是错误的;

所以当x是叶子的时候,f[y]=d[y]+flow(x,y);

#include<bits/stdc++.h>
using namespace std;
const int N=210010;
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch))     {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
} 
int T,n,m,tot,x,y,v,ans,lin[N],d[N],f[N],du[N];
struct gg {
    int y,next,v;
}a[N<<1];
inline void add(int x,int y,int v) {
    a[++tot].y=y;
    a[tot].next=lin[x];
    lin[x]=tot;
    a[tot].v=v;
}
inline void dp(int x,int fa) {
    d[x]=0;
    for(int i=lin[x];i;i=a[i].next) {
        int y=a[i].y;
        int v=a[i].v; 
        if(y==fa) continue;
        dp(y,x);
        if(du[y]==1) d[x]+=v;
        else d[x]+=min(d[y],v);
    }
    return;
}
inline void dfs(int x,int fa) {
    for(int i=lin[x];i;i=a[i].next) {
        int y=a[i].y;
        if(y==fa) continue;
        if(du[x]==1) f[y]=d[y]+a[i].v;
        if(du[x]>1) f[y]=d[y]+min(f[x]-min(a[i].v,d[y]),a[i].v);
        dfs(y,x);
    }
} 
int main() {
    //freopen("1.in","r",stdin);
    read(T);
    while(T--) {
        read(n);
        tot=0;
        memset(du,0,sizeof(du));
        memset(d,0,sizeof(d));
        memset(lin,0,sizeof(lin));
        memset(f,0,sizeof(f));
        for(int i=1;i<n;i++) {
            read(x); read(y); read(v);
            add(x,y,v); add(y,x,v);
            ++du[x]; ++du[y];
        }
        dp(1,0);
        f[1]=d[1];
        dfs(1,0);
        ans=0;
        for(int i=1;i<=n;i++) {
            ans=max(ans,f[i]);
        }
        cout<<ans<<endl;
    }
    return 0;
} 
View Code

对于刚才那个题目 我们显然可以用树形dp+换根解决,我们有f[i]表示以i为根的子树内部 最大值 g[i][表示作为整颗树的根 比较懊悔的是 考场上想到了 但是没敢写;

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch))     {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
} 
const int N=1000010; 
struct gg {
    int y,next;
}a[N<<1];
int n,m,x,y,w[N],lin[N],tot,f[N],g[N],mark[N],ans=-(1<<30);
inline void add(int x,int y) {
    a[++tot].y=y;
    a[tot].next=lin[x];
    lin[x]=tot;
}
inline void dp(int x,int fa) {
    f[x]=w[x];
    for(int i=lin[x];i;i=a[i].next) {
        int y=a[i].y;
        if(y==fa) continue;
        dp(y,x);
        if(f[y]>0) {
            f[x]+=f[y];
            mark[y]=1;
        }
    }
}
inline void dfs(int x,int fa) {
    for(int i=lin[x];i;i=a[i].next) {
        int y=a[i].y;
        if(y==fa) continue;
        if(mark[y]) g[y]=max(f[y],g[x]-f[y]);
        else g[y]=max(0,g[x])+f[y];
        dp(y,x);
    }
}
int main() {
    read(n);
    int flag=0;
    for(int i=1;i<=n;i++) {
        read(w[i]);
        w[i]=w[i]==1?1:-1;
        if(i>1) if(w[i]!=w[i-1]) flag=1;
    }
    for(int i=1;i<n;i++) {
        read(x); read(y);
        add(x,y); add(y,x);
    }
    if(!flag) {
        cout<<n<<endl;
        return 0;
    }
    dp(1,0);
    g[1]=f[1];
    dfs(1,0);
    for(int i=1;i<=n;i++) {
        ans=max(ans,g[i]);
        w[i]=-w[i];
        mark[i]=g[i]=f[i]=0;
    }     
    dp(1,0);
    g[1]=f[1];
    dfs(1,0);
    for(int i=1;i<=n;i++) 
        ans=max(ans,g[i]);
    cout<<ans<<endl;
    return 0;
} 
View Code

然后题解上有一种比较巧妙的思路,然后是直接维护子树内差值的最大值;把0的点作为-1,也就是一个维护正的最大值 一个维护负的最小值 这样在绝对值的情况下是最大值;

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch))     {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
} 
const int N=110000;
int n,m,x,y,tot,a[N],lin[N],f[N],g[N];
struct gg {
    int y,next;
}e[N<<1];
inline void add(int x,int y) {
    e[++tot].y=y;
    e[tot].next=lin[x];
    lin[x]=tot;
}
inline void dfs(int x,int fa) {
    g[x]=f[x]=a[x];
    for(int i=lin[x];i;i=e[i].next) {
        int y=e[i].y;
        if(y==fa) continue;
        dfs(y,x);
        f[x]+=max(0,f[y]);
        g[x]+=min(0,g[y]);
    }
}
int main() {
    freopen("trip.in","r",stdin);
    freopen("trip,out","w",stdout);
    read(n);
    for(int i=1;i<=n;i++) {
        read(a[i]);
        a[i]=a[i]==0?-1:a[i];
    }
    for(int i=1;i<n;i++) {
        read(x); read(y);
        add(x,y); add(y,x);
    }
    dfs(1,1); 
    int ans=0;
    for(int i=1;i<=n;i++) {
        ans=max(ans,f[i]);
        ans=max(ans,-g[i]);
    }
    cout<<ans<<endl;
    return 0;
}
View Code

T2 昨天被刘神D没学过数学hhh

题目大意:给定 n, m,询问有多少个字符集大小为 m 的字符串满足长度大于 1 的前缀中只有 n 为回文串。

我最开始一个东西搞错了 整个递推式都错了qwq;

那就是先考虑所有回文串的个数是多少个:答案显然是m的n/2次方;

现在我就bb我当时的错误思路:对于一个回文串 显然我们只考虑他的一半就可以了 所以剩余n/2个位置 然后有m个备选集合 如果你想要的选出来的数字各不相同 这显然是个排列数A(m,n/2);

但是我当时考虑 现在我想选出来相同的数字怎么办.... 不妨我将备选集合扩大n/2倍 每个数字不就能被选出来多次了嘛 看着好对啊

其实你原来的集合是{1,2,3,...,m} 扩大后你的理想状态是{1,2,3,...,m,1,2,3,...,m,.....,m} 当然集合具有互异性 然后这样即使会有多个1被选出来 但是他们也是本质不同的数字了 不妨这样理解

现在已经是11,12,...1m,已经是本质不同的数字了 所以显然我们对于一个数他会被多次统计 而作为不同的情况被累加 所以错误了;

好了那不看我的错误思路了 我们考虑已经求出来所有的回文串的个数了 考虑怎么求不合法的情况下;

那就是 我们设f(i)表示当前长度为i并且满足条件的回文串的个数,我们可以发现一个递推式;

f(n)=mn/2-∑ i<4/n f(i)*m(n-2*i)/2,然后就是比较困惑的一点了 题解上面的递推式有点不一样;下面这个 qwq 不是很明白 晚上讨论的时候在说吧;

我还是说对于我那个式子 我们考虑前缀和优化一下,就能(n)的复杂度了;

#include<bits/stdc++.h>
using namespace std;
typedef long long ll; 
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch))     {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
} 
const int mod=1e9+7;
const int N=110000;
int n,m,pw[N],f[N];
int g[N];//前缀和数组; 
inline int pop(int a,int b){a-=b;return a<0?a+mod:a;}
inline int add(int a,int b){a+=b;return a>=mod?a-mod:a;}
inline int mul(int a,int b){return (ll)a*b%mod;}
int main() {
    freopen("string.in","r",stdin);
    freopen("string.out","w",stdout);
    read(n); read(m);
    pw[0]=1;
    for(int i=1;i<=n;i++) pw[i]=mul(pw[i-1],m);
    for(int i=1;i<=n;i++) {
        f[i]=pop(pw[(i+1)/2],g[(i+1)/2]);
        if(i>1) g[i]=add(f[i],mul(g[i-1],m));//对于处理这个点缀和; 
    }
    cout<<f[n]<<endl;
    return 0;
}
View Code

9.15

T1:考场上暴力解了excrt 然后求了方案数 大致是nlogn的复杂度 然后 考试快结束的时候 我的电脑系统自己把自己删掉了 比较nb把 我就不放考场代码了;

最后 这是个结论题 我丢 我太难了; 

不妨复习一下exgcd相关的知识 我的数学比较垃圾qwq; 

原文地址:https://www.cnblogs.com/Tyouchie/p/11541244.html