NOIP 模拟 16
A 图
直接上 std::bitset
。
B 序列
首先赋值在加法前,加法在乘法后,一个有效的赋值可以看做一个加法,乘法的顺序无所谓,直接加最大,考虑把加法转化成乘法,那就看加的数在原数的占比,需要考虑加法的顺序,一定是先加大的,所以直接排序后转化成乘法就好了。
C 树
究极换根 DP 好题。
先看 \(D=1\),我们可以直接换根求出每个点做起点的情况以及这个点对根的影响,然后直接暴力统计即可。
正解就是在 \(D=1\) 的基础上处理一些更多的信息,或者说把 \(D=1\) 的步骤普适化。
求答案实际上需要的是连的树的必败点和必胜点方案数,以及对根有影响的节点数。先不考虑连第一棵树,这样每棵树都可以任意起点,设 \(f_{i,0/1}\) 表示连接了 \(i\) 棵数,当前树上必败/必胜点的方案数。发现只有连必败点才有可能改变信息,设 \(n_{0/1}\) 表示给 \(i\) 连一个必败点后整棵树上的必败/胜点数量,\(num_{0/1}\) 表示原有的必败/胜点数量,所以转移就是:
这个东西可以轻易矩阵加速,所以现在只需要考虑如何处理出来 \(num\) 和 \(n_{0/1}\),\(n_{0/1}\) 就是换根的时候记一下即可,\(num\) 的处理比较困难。
首先,\(num_i\) 很难处理,因为会有很多连接关系的影响,并且这些影响要连续起来,考虑处理一个 \(f_{i,0/1}\) 表示连接一个必败点使得 \(i\) 节点为必败/胜的点的数量。不难发现 \(\sum_{i=1}^nnum_i=\sum_{i=1}^nf_i\),然后考虑如何换根。
换根需要拆贡献,博弈论通常把记一下 \(fn_i\) 表示 \(i\) 的子节点中必败点的数量。首先需要处理出子树情况的各种信息,考虑一个点是否对父亲有影响就可以处理出来 \(f\),然后换根也是需要考虑影响关系,如果父亲有 \(0\) 个或者大于两个必败点,那么直接换根即可,否则影响关系会变,如果儿子同时是必败点,那么就需要重新处理父亲的信息,封装一个转移函数就好写了,具体看代码吧。
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pii std::pair<int,int>
#define eb emplace_back
#define pb push_back
typedef long long ll;
typedef unsigned long long ull;
std::mt19937 myrand(std::chrono::high_resolution_clock::now().time_since_epoch().count());
inline int R(int n){return myrand()%n+1;}
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);return x*f;}
const int N=2e5+10,mod=1e9+7,inf=1e9;
inline void Min(int &x,int y){if(x>y)x=y;}
inline void Max(int &x,int y){if(x<y)x=y;}
inline void W(int &x,int y){x=(x+y)%mod;}
int n,num[2],D,n0,n1;
std::vector<int> e[N];
struct NODE{int num,f,g0,g1;}a[N],r[N],zc[N];
inline void Pass(int x,int fa){
a[x]={0,0,0,0};
for(int v:e[x]){
if(v==fa)continue;
a[x].f|=!a[v].f;a[x].num+=!a[v].f;
}
a[x].g1++;
for(int v:e[x]){
if(v==fa)continue;
if(a[x].num>1)W(a[x].g1,a[v].g0+a[v].g1);
if(a[x].num==1){
if(a[v].f)W(a[x].g1,a[v].g0+a[v].g1);
else W(a[x].g0,a[v].g1),W(a[x].g1,a[v].g0);
}if(!a[x].num)W(a[x].g1,a[v].g0),W(a[x].g0,a[v].g1);
}
}
inline void dfs(int x,int fa){for(int v:e[x])if(v^fa)dfs(v,x);Pass(x,fa);zc[x]=a[x];}
inline void change(int x,int fa){
if(x==1)r[x]=a[x];
else{
if(!a[x].f){
if(a[fa].num<=2)Pass(fa,x);
else a[fa].num--,a[fa].g1-=a[x].g0+a[x].g1;
}else{
if(a[fa].f)a[fa].g1-=a[x].g0+a[x].g1;
else a[fa].g1-=a[x].g0,a[fa].g0-=a[x].g1;
}
Pass(x,0);r[x]=a[x];
}
for(int v:e[x])if(v^fa)change(v,x),a[x]=r[x];a[x]=zc[x];
}
struct MAT{
int a[2][2];
inline MAT(){memset(a,0,sizeof(a));}
inline MAT operator*(const MAT&B)const{
MAT res;int r;for(int i=0;i<2;++i)
for(int k=0;k<2;++k){
r=a[i][k];for(int j=0;j<2;++j)W(res.a[i][j],r*B.a[k][j]);
}return res;
}
}base,ans;
inline void work(){
for(int i=1;i<=n;++i)num[r[i].f]++,W(n0,r[i].g0),W(n1,r[i].g1);
ans.a[0][0]=num[0],ans.a[0][1]=num[1];
base.a[0][0]=n0,base.a[1][0]=n*num[0]%mod,base.a[0][1]=n1,base.a[1][1]=n*num[1]%mod;
for(;D;D>>=1,base=base*base)if(D&1)ans=ans*base;int ANS=r[1].g1*ans.a[0][0]+n*ans.a[0][1]*r[1].f;
std::cout<<(ANS%mod+mod)%mod<<'\n';
}
signed main(){
freopen("c.in","r",stdin);freopen("c.out","w",stdout);
std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
n=read();D=read()-1;for(int i=1;i<n;++i){int u=read(),v=read();e[u].eb(v);e[v].eb(u);}
dfs(1,0);change(1,0);work();
}
D 字符串
简单手玩发现相邻的两个字母如果逆序一定会产生一个贡献,然后暴力线段树维护 \(k^2\) 中情况就做完了。
总结
打成傻逼了,T2 忘了特判挂了 60pts,T4 根本没去仔细观察,给自己设限了,傻卵。