先订正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; }
对于刚才那个题目 我们显然可以用树形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; }
然后题解上有一种比较巧妙的思路,然后是直接维护子树内差值的最大值;把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; }
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; }
9.15
T1:考场上暴力解了excrt 然后求了方案数 大致是nlogn的复杂度 然后 考试快结束的时候 我的电脑系统自己把自己删掉了 比较nb把 我就不放考场代码了;
最后 这是个结论题 我丢 我太难了;
不妨复习一下exgcd相关的知识 我的数学比较垃圾qwq;