t1 对数
有的人可以推出动态规划的柿子(鸡),有的人可以直接看出找规律的柿子(鱼),而卑劣阴暗的老鼠只能想着动态规划推半天推出了比正常东西难调一点的别人直接找规律能看出来的东西。
首先考虑什么时候 \(x\oplus y=x|y\),显然是不能存在一位 \(x/y\) 二进制都为 \(1\),看到数据范围不妨考虑数位 dp。那么应该先考虑 \(0\leq x,y\leq 2^{len}-1\) 的情况,对于一个 \(x\) 来说,其为 \(1\) 的位上 \(y\) 一定是 \(0\),否则随意,总方案数应该是枚举有多少个 \(0\) 然后加起来 \(\sum_{i=0}^{len}\binom{n}{i}\times 2^{len}=3^{len}\)。
现在考虑钦定一个前缀带来的贡献,首先应该特殊处理 \(x=y=0\) 之后给前缀应该加上一个匹配更小的数的约束来防止 \(y\) 超过 \(R\),最后倍增答案即可。分情况讨论,如果钦定前缀的开头是 \(1\),那么为 \(1\) 的位必须对应 \(0\) 别的随意就是更小的,乘上后缀 \(3\) 的幂就是方案数,如果钦定前缀开头是 \(0\),这种只有没有前缀相等一种,后面可以随便选贡献是一个不用乘 \(2\) 的 \(3^{n-1}\)。
#include<bits/stdc++.h>
#define int long long
#define up(i,l,r) for(int i=l; i<=r; ++i)
#define dn(i,r,l) for(int i=r; i>=l; --i)using namespace std;const int N=1000005, P=998244353;int n, pw2[N], pw3[N], Ans, cnt;
char str[N];signed main() {freopen("pair.in","r",stdin);freopen("pair.out","w",stdout);ios::sync_with_stdio(0);cin.tie(0);cin >> (str+1), n=strlen(str+1), pw2[0]=pw3[0]=1;up(i,1,n) pw2[i]=2*pw2[i-1]%P, pw3[i]=3*pw3[i-1]%P;up(i,2,n) {if(str[i]=='0') ++cnt;if(str[i]=='1') (Ans+=pw3[n-i]*pw2[cnt+1]%P)%=P;}(Ans+=pw2[cnt])%=P;cout << (2*Ans%P+pw3[n-1])%P << '\n';return 0;
}
T2 联通图
眼睛题,有人看错题+不知道数据范围浪费了一两个小时我不说是谁,看对了马上就会坐了,然后其实在看对题目的情况下这个题目瞎子可能会不好做。
这个题目感觉比较需要手玩来熟悉题目流程性质,手玩之后可以变成下述便于观察的形式。用一种好看的画图方式,称原点为白点,新加入的点为黑点,将黑点画在白点旁边保持树的形状,一个时刻的连边的规则如下:
- 白点与白点连边
- 黑点邻居连边,如果有黑点连向黑点否则连向白点
因为连边只向邻域,有一个比较显然的观察,一条树边两边两部分在这个图上关联性容易剥离,意思是不会有跨过去的环的贡献。进一步的,点与点之间的两条边最多能合并住 \(3\) 个连通块,那么树形 dp 子树里面最多有两个连通块,设计一个 dp 一个 \(f_{i,1/2}\) 表示子树 \(i\) 里面有 \(1/2\) 个连通块就非常的对了,转移还是很简单的分讨,就是 lgjoj 好像卡 vector
存前后缀积的常。
#include<bits/stdc++.h>
#define int long long
#define up(i,l,r) for(int i=l; i<=r; ++i)
#define dn(i,r,l) for(int i=r; i>=l; --i)
#define pb push_backusing namespace std;const int N=5005, P=998244353;int n, col[N], f[N][3], L[N], R[N];
vector<int> to[N]; inline void mul(int &a,int b) { a=a*b%P; }
inline void add(int &a,int b) { a=(a+b)%P; }void dp(int x,int fad) {if(col[x]==0) f[x][1]=1, f[x][2]=0; if(col[x]==1) f[x][1]=0, f[x][2]=1;if(fad&&to[x].size()==1) return;for(int y:to[x]) if(y!=fad) {dp(y,x);if(col[x]==0) {if(col[y]==0) mul(f[x][1],f[y][1]);if(col[y]==1) mul(f[x][1],(2*f[y][1]%P+f[y][2])%P);}if(col[x]==1) {if(col[y]==0) mul(f[x][2],2*f[y][1]%P);if(col[y]==1) mul(f[x][2],(2*f[y][1]%P+f[y][2])%P);}}if(col[x]==1) {int cnt=0, ran=0;for(int y:to[x]) if(y!=fad) {++cnt;if(col[y]==0) L[cnt]=R[cnt]=2*f[y][1]%P;if(col[y]==1) L[cnt]=R[cnt]=(2*f[y][1]%P+f[y][2])%P;}L[0]=R[cnt+1]=1;up(i,1,cnt) L[i]=L[i-1]*L[i]%P;dn(i,cnt,1) R[i]=R[i+1]*R[i]%P;for(int y:to[x]) if(y!=fad) {++ran;add(f[x][1],L[ran-1]*R[ran+1]%P*f[y][1]%P);}}
}signed main() {freopen("tree.in","r",stdin);freopen("tree.out","w",stdout);ios::sync_with_stdio(0);cin.tie(0);cin >> n;up(i,2,n) {int u, v;cin >> u >> v;to[u].pb(v);to[v].pb(u);}up(i,2,n) {int x;cin >> x;col[x]=1;dp(1,0);cout << f[1][1] << '\n'; }return 0;
}
T3 函数
看着肯定是要先找结论弄点充要条件了,我不相信自己的智力水平能稳定快速做出来这类东西,可以打个表辅助一下观察,先根据部分分的提示考虑 \(a_i=i\) 的情况。
首先有一些很显然的分析,先上一个有脑去重以防万一,模数 \(x_1>x_2>\dots>x_m\),然后 \(a’_i<x_m\) 且 \(i\in[1,x_m),a'_i=i\) 以及 \(a_{x_m}=0\)。
#include<bits/stdc++.h>
#define int long long
#define up(i,l,r) for(int i=l; i<=r; ++i)
#define dn(i,r,l) for(int i=r; i>=l; --i)using namespace std;const int N=5005;int n, a[N];signed main() {
// freopen("chk.out","w",stdout);mt19937_64 rd(chrono::steady_clock::now().time_since_epoch().count());chrono::steady_clock::now().time_since_epoch().count();
// ios::sync_with_stdio(0);
// cin.tie(0);n=20;up(i,1,n) a[i]=i;int x=n;for(int x:{3,5,7}) {cout << "mod " << x << '\n';up(i,1,n) a[i]%=x;cout << "A "; up(i,1,n) printf("%d ",a[i]); cout << '\n';} cout << "zero "; dn(i,n,1) if(a[i]==0) cout << i << ','; cout << '\n';return 0;
}
很难不发现答案形式为 \(a_i=a_{i-1}\) 或者 \(a_i=0\),很难不猜到对于一个 \(a'\),只要把 zero 那一串放进循环的 vector
里面就可以构造出来相同的 \(a'\)。发现这个合法解怎么构造跟判定没有任何关系哈哈哈。
然后发现 \(a’_i<x_m / i\in[1,x_m) / a'_i=i / a_i=a_{i-1} 或者 a_i=0\) 凑在一起就是充要的了,构造方式如上,证明也比较简单,因为不超过 \(x_m\) 个就会有一个 \(0\),所以每一次只会掉其本身没有可以影响到的倍数,别的都很自然。
现在问题是怎么去重力,首先枚举 \(x(x_m)\),考虑 \(f_{i,j}\) 表示考虑了 \(a'_1,\dots,a'_i\) 且 \(a'_i=j\) 时的方案数,为了去重应该钦定有 \(a'_i\) 碰到过 \(x-1\),转移非常简单但是题解真的还挺误导人的不嘻嘻,具体而言要写个分讨,\(0/1\) 的部分我不赘述了:
- \(a_i-j<=a_{i-1}\),此时若 \(a'_i=j\) 则有 \(a'_{i-1}=j+a_{i-1}-a_i\),转移 \(f_{i-1,j+a_{i-1}-a_i}\to f_{i,j}\)
- \(a_i-j>a_{i-1}\),此时 \(a'_{i-1}\) 选什么都行,则有 \(o\in[0,x),f_{i-1},o\to f_{i,j}\)
初始化的时候去掉 \(\leq x\) 的部分然后钦定初始前继为 \(x\),答案是 \(\sum_{x}\sum_{i=0}^{x-1}fx_{n,i}\)。
#include<bits/stdc++.h>
#define int long long
#define up(i,l,r) for(int i=l; i<=r; ++i)
#define dn(i,r,l) for(int i=r; i>=l; --i)
#define pb push_backusing namespace std;const int N=505, P=998244353;int n, a[N], f[N][N][2], Ans;inline void add(int &a,int b) { a=(a+b)%P; }signed main() {freopen("mod.in","r",stdin);freopen("mod.out","w",stdout);ios::sync_with_stdio(0);cin.tie(0);cin >> n;up(i,1,n) cin >> a[i];sort(a+1,a+1+n), n=unique(a+1,a+1+n)-a-1;up(x,1,501) { int pos=1, flag=0, w0=0, w1=0, tmp;up(i,1,n) if(a[i]==x-1) flag=1;while(pos<=n&&a[pos]<=x) ++pos; memset(f,0,sizeof(f));tmp=a[pos-1], a[pos-1]=x;f[pos-1][0][flag]=1;if(flag) w1=1; else w0=1;up(i,pos,n) {up(j,0,x-1) {if(a[i]-j<=a[i-1]) {if(j==x-1) {add(f[i][j][1],f[i-1][j+a[i-1]-a[i]][0]);add(f[i][j][1],f[i-1][j+a[i-1]-a[i]][1]);}else {add(f[i][j][0],f[i-1][j+a[i-1]-a[i]][0]);add(f[i][j][1],f[i-1][j+a[i-1]-a[i]][1]);}}else {if(j==x-1) add(f[i][j][1],w0), add(f[i][j][1],w1);else add(f[i][j][0],w0), add(f[i][j][1],w1);}}w0=w1=0;up(j,0,x-1) add(w0,f[i][j][0]), add(w1,f[i][j][1]);}add(Ans,w1), a[pos-1]=tmp;}cout << (Ans%P+P)%P;return 0;
}