文章目录
- 考试复盘
- 染色问题
- 芬威克树
- 礼物
考试复盘
先说T1T1T1
染色,以为是道数学题,推了有一会儿的公式,从颜色1到颜色m,感觉是dpdpdp转移
发现颜色重叠的方案可以转化为另外一种相邻不重叠的染色
但是推到颜色4的时候就发现自己考虑掉了一些情况
最后得出应该是做不出来了
往后做了,又跑回来敲暴力,半天发现自己暴力敲出来都很困难
再说T2T2T2
推了整整一张的lowbitlowbitlowbit发现了某些跟kkk进制和取模kkk的一些性质吧
先把暴力敲了
然后推广n≤1e9n\le 1e9n≤1e9,然后就卡住了
半路转回去看暴力,发现自己对树状数组找的规律出现了偏差——打表发现的,仓促改了
才勉强保住了202020的暴力
对于40%40\%40%的部分分也有想过放到树上,将iii和i+lowbit[i]i+lowbit[i]i+lowbit[i]连边,然后线段树维护一下
哎!应该再认真细想一下下,就发现其实是非常好维护的啊!白丢202020
当时莽正解去了
最后说T3T3T3
我真的很讨厌很不会这种环旋转同构的题,啊啊啊啊啊啊啊啊
刚看到环就打算破环为链嘛,毕竟是老套路
然后旋转同构,就想到了昨天做的第三题,zjx大佬讲得减去前一个出现的位置处理
考虑将宝石的位置相减,然后sortsortsort位置差,判断重构
但是很快发现自己无法破环为链处理n−1n-1n−1这两个宝石的衔接
导致暴力也敲不出来
只能拿m=0m=0m=0和n≤4n\le 4n≤4暴力打表的分
万万没想到这种旋转等价类就是polyapolyapolya应用老套路!!
俺哪知道polyapolyapolya??寒假期间lj讲过,但是当时就没学懂
这次我认了——害,今天要把polyapolyapolya补一下!!
总结一下
这一场的考试单不提难度问题,毕竟没有人知道下一场是简单还是难到抠jio
但就自己考试心态以及策略上的反思
第一次通读了三道题后,很明显感受到自己对T1,T3T1,T3T1,T3的方案数求解之类问题,尤其是T1T1T1完全做不来
就花了挺多的时间搞T2T2T2,最后还是呈现的暴力
T1,T3T1,T3T1,T3之间多次反复横跳,敲暴力,敲了又删又改,再敲……
完全没有冷静下来想想如何敲暴力,以及自己是否能敲出来,还有暴力的正确性
- 评估题目难度后,若觉得绝对无果,就弄暴力
- 暴力更要思考一些细节,评估自己能否敲出来,以及验证暴力的正确性。确定🆗后再动手敲
- 选择自己最有可能淦出来的题,根据自己能力分配时间,啃死了做不出来的题就不要回去看了,绝对不能再出现反复横跳不断切换思路,整个人没有静下来的情况!!!
- 就算没有一道题能拿更多的暴力分,也要想尽办法拿基础的暴力分,并且不要看不起4/54/54/5分,不气馁,不妥协,不慌张,尽可能多拿每一分
- 考试时间长,后半部分更要打起精神,切忌出现后面走神分心,今天就出现了两三次,一定要迅速拉回来
- 以自己的水平来看,能完整淦出一道题的几率小之又小,所以应该一点点蚕食,从少到多,从小到大一点点把部分分慢慢拿走,最后获得一个可观的部分分总和
总而言之,今天的考试反思就是当做一道题的时候就认认真真专注地啃,不要在此期间去想其他的题目。不要在题目间反复横跳,一会儿做这个,一会儿做那个,除非灵光乍现,当然我一般是不可能的。
今天暴露出来的知识问题
- dpdpdp果然还是自己比较薄弱的板块
- 与环有关的题目
- 统计方案数——一般都跟数学挂钩的题目
- FFT,NTTFFT,NTTFFT,NTT的应用还是不太行啊
- polyapolyapolya定理
- 好像很多的样子……
抓紧时间孑孓!!!今晚至少要把polyapolyapolya重新学懂
染色问题
目前只会50%50\%50%的部分分
不考虑先前染的颜色被覆盖这件事情
如果某种颜色在最终的序列中出现了xxx次,就直接认为在染这种颜色的时候,只染了xxx个格子
但这样一来每次染色的格子就不再是连续的一段了
巧妙的把给一段格子染色认为是在已被染色的颜色序列中插入一段
设fi,jf_{i,j}fi,j表示前iii次染色,已有颜色序列长度为jjj的方案数
则有转移 想了很久,是晚上去逛操场走圈圈时想懂的
fi,j=fi−1,j+∑k=0j−1fi−1,k×(k+1)f_{i,j}=f_{i-1,j}+\sum_{k=0}^{j-1}f_{i-1,k}\times (k+1)fi,j=fi−1,j+k=0∑j−1fi−1,k×(k+1)
- iii颜色染完后被覆盖,相当于没染过,fi−1,jf_{i-1,j}fi−1,j
- 枚举iii颜色染得长度j−kj-kj−k,因为不能出现大颜色中间夹杂小颜色的不合法情况(2,1,2)(2,1,2)(2,1,2),所以当第iii颜色插入的时候,一定是(…,i,i,…,i,i…..)(…,i,i,…,i,i…..)(…,i,i,…,i,i…..) 第一个iii前面的长度可能为[0,k][0,k][0,k],剩下的就是最后一个iii后面的,所以是×(k+1)\times (k+1)×(k+1)
最后一次染色的长度必须非000,fm,jf_{m,j}fm,j不能从fm−1,jf_{m-1,j}fm−1,j转移过来,ififif一下就行了,答案即为fm,nf_{m,n}fm,n
芬威克树
直接翻译代码可得202020,√
n,q≤2e5n,q\le 2e5n,q≤2e5,404040 √
考虑将每个点xxx向x+lowbit(x)x+lowbit(x)x+lowbit(x)连边,如果后者超过了nnn就改为连向一个超级根
这样树状数组就变成了一棵有根树
而一次修改操作是把一个点到根路径上的每个点的权值异或上vvv,查询是单点查询权值
随便树状数组或者线段树维护dfndfndfn序即可做到
kkk为奇数 √
在这个情况连出来的树,跳lowbitlowbitlowbit操作的本质相当于xxx的最低非零位不断×2\times 2×2并进位
发现在kkk是奇数的时候,xxx的最低非零位永远不会变,因为2x≠0modk2x≠ 0\ mod \ k2x=0 mod k对于任何一个小于kkk的xxx都成立,同理在模意义下222的逆元也一定存在,所以每个xxx都只会有只多一个后继
发现连出来的这棵树一定是根节点分叉的若干条链,而每次操作都是给链的一段后缀异或上vvv
所以对于每条链用个数据结构维护一下即可,类似前缀和
kkk为偶数, 此时不能保证[1,n][1,n][1,n]的所有点被连成若干条链
但将kkk拆分为2p×t,t2^p\times t,t2p×t,t为奇数
所有满足最低非零位上的值包含的222的因子个数不小于ppp的点会连成若干条链
接下来只需要考虑剩下的点
考虑每暴力走一步最低非零位值包含的222的因子个数就会+1+1+1
那么在最低位不变的情况下最坏只需要暴力走ppp次(modkmod\ kmod k不会使222的因子个数减少)
当然可能走着走着发现最低非零位乘222变成000了,这时候最低非零位就发生了改变
不过这样的改变至多只有logknlog_knlogkn次
所以一次暴力往上走的复杂度就是O(plogkn)=O(log2klogkn)=O(log2n)O(plog_kn) =O(log_2klog_kn) =O(log_2n)O(plogkn)=O(log2klogkn)=O(log2n)
n≤1e9n\le 1e9n≤1e9
大下标可以动态开点线段树
现在的思路是对于一个点xxx,如果在链上,标记只打在这个点上就返回,查询用前缀和
如果不在链上,在支链上,以后或许会有升级到链上的问题,就暴力更新跳上去
如果想要ACACAC
最后只剩下如何定位一个点xxx在哪一条链上,链头的位置
#include <cstdio>
#include <iostream>
using namespace std;
#if(__cplusplus == 201103L)
#include <unordered_map>
#include <unordered_set>
#else
#include <tr1/unordered_map>
#include <tr1/unordered_set>
namespace std {using std :: tr1 :: unordered_map;using std :: tr1 :: unordered_set;
}
#endif
#define maxn 2000005
#define Base 30
struct node {int l, r, ans;node(){ l = r = ans = 0; }
}tree[maxn * 30];
unordered_map < int, int > mp;
int n, Q, k, p, t, cnt;
int mi[Base + 5];
int f[maxn][Base + 5], g[maxn][Base + 5], F[maxn][Base + 5], C[maxn][Base + 5];int lowbit_id( int x ) {if( k == 2 ) return x;while( x % k == 0 ) x /= k;return x % k;
}int lowbit_val( int x ) {if( k == 2 ) return x & ( -x );int num = 1;while( x % k == 0 ) x /= k, num *= k;return ( x % k ) * num;
}int GetP( int x ) {int num = 0;while( ! ( x & 1 ) ) x >>= 1, num ++;return num;
}int GetC( int x ) {int num = 0, w = 1;while( x % k == 0 ) x /= k;x /= k;while( x ) {num += w * ( x % k );x /= k;w *= k;}return num;
}void init() {mi[0] = 1;for( int i = 1;i <= Base;i ++ ) mi[i] = mi[i - 1] << 1;p = GetP( k ), t = k / mi[p];for( int i = 1;i < t;i ++ ) {f[i][0] = ( i + i ) % t, F[f[i][0]][0] = i;g[i][0] = ( i + i > t ), C[f[i][0]][0] = g[i][0];}for( int j = 1;j <= Base;j ++ ) {for( int i = 1;i < t;i ++ ) {f[i][j] = f[f[i][j - 1]][j - 1], F[f[i][j]][j] = i;g[i][j] = g[f[i][j - 1]][j - 1] + g[i][j - 1], C[f[i][j]][j] = g[i][j];}}
}void modify( int &num, int l, int r, int pos, int val ) {if( ! num ) num = ++ cnt;tree[num].ans ^= val;if( l == r ) return;int mid = ( l + r ) >> 1;if( pos <= mid ) modify( tree[num].l, l, mid, pos, val );else modify( tree[num].r, mid + 1, r, pos, val );
}int query( int num, int l, int r, int L, int R ) {if( ! num ) return 0;if( L <= l && r <= R ) return tree[num].ans;int mid = ( l + r ) >> 1, ans = 0;if( L <= mid ) ans ^= query( tree[num].l, l, mid, L, R );if( mid < R ) ans ^= query( tree[num].r, mid + 1, r, L, R );return ans;
}pair < int, int > find( int x ) {int c = GetC( x ), now = lowbit_id( x ), nxt = 0;now /= mi[p];for( int i = Base;~ i;i -- ) {if( C[now][i] <= c ) {c -= C[now][i];now = F[now][i];nxt += ( 1 << i );}}now *= mi[p];while( x % k == 0 ) x /= k, now *= k;return make_pair( now, nxt );
}int calc( int x ) {if( GetP( lowbit_id( x ) ) < p ) {if( mp.count( x ) ) return mp[x];else return 0;}pair < int, int > top = find( x );if( mp.count( top.first ) ) return query( mp[top.first], 0, 1e9, 0, top.second );else return 0;
}void insert( int x, int val ) {while( x <= n && GetP( lowbit_id( x ) ) < p ) {if( mp.count( x ) ) mp[x] ^= val;else mp[x] = val;x += lowbit_val( x );}if( x <= n ) {pair < int, int > top = find( x );int rt;if( mp.count( top.first ) ) rt = mp[top.first];else rt = mp[top.first] = ++ cnt;modify( rt, 0, 1e9, top.second, val );}
}int query( int x ) {int ans = 0;for( int i = x;i;i -= lowbit_val( i ) )ans ^= calc( i );return ans;
}int main() {scanf( "%d %d %d", &n, &Q, &k );init();while( Q -- ) {int opt, x, y;scanf( "%d %d", &opt, &x );if( opt == 1 ) {scanf( "%d", &y );insert( x, y );}else printf( "%d\n", query( x ) );}return 0;
}
礼物
循环同构套polyapolyapolya,答案为
∑d∣gcd(n,m)f(nd,md)ϕ(d)n\frac{\sum_{d|gcd(n,m)}f(\frac{n}{d},\frac{m}{d})\phi(d)}{n}n∑d∣gcd(n,m)f(dn,dm)ϕ(d)
f(n,m)f(n,m)f(n,m)表示将nnn个珠子中的mmm个变成金的,且最长连续段不超过kkk,首尾连续段长度之和也不超过kkk的方案数
考虑未变金的剩下n−mn-mn−m个珠子,因为是环所以有n−mn-mn−m个间隙
相当于要在n−m−1n-m-1n−m−1个间隙以及首尾处(特殊的一个间隙单独处理)中放入mmm个金珠子
类似将连续段的金珠子缩成一个点的思想
很容易写出生成函数,每一个间隙(除掉首尾特殊判断)的可能为∑i=0kxi\sum_{i=0}^k x^i∑i=0kxi
首尾的生成函数为∑i=0k(i+1)xi\sum_{i=0}^k(i+1)x^i∑i=0k(i+1)xi
则F(x)=(∑i=0kxi)n−m−1⋅(∑i=0k(i+1)xi)F(x)=(\sum_{i=0}^kx^i)^{n-m-1}·(\sum_{i=0}^k(i+1)x^i)F(x)=(∑i=0kxi)n−m−1⋅(∑i=0k(i+1)xi)
答案为[xm]F(x)[x^m]F(x)[xm]F(x)
如果停在这里,暴力多项式乘法以及前缀和优化就是O(n2)O(n^2)O(n2)
接下来是正解的处理
令G(x)=∑i=0k(i+1)xiG(x)=\sum_{i=0}^k(i+1)x^iG(x)=i=0∑k(i+1)xi
则xG(x)+∑i=0k+1xi=G(x)+(k+2)xk+1xG(x)+\sum_{i=0}^{k+1}x^i=G(x)+(k+2)x^{k+1}xG(x)+i=0∑k+1xi=G(x)+(k+2)xk+1
解得G(x)=1+(k+1)xk+2−(k+2)xk+1(1−x)2G(x)=\frac{1+(k+1)x^{k+2}-(k+2)x^{k+1}}{(1-x)^2}G(x)=(1−x)21+(k+1)xk+2−(k+2)xk+1
代回F(x)F(x)F(x)
F(x)=(∑i=0kxi)n−m−1⋅1+(k+1)xk+2−(k+2)xk+1(1−x)2F(x)=(\sum_{i=0}^kx^i)^{n-m-1}·\frac{1+(k+1)x^{k+2}-(k+2)x^{k+1}}{(1-x)^2}F(x)=(i=0∑kxi)n−m−1⋅(1−x)21+(k+1)xk+2−(k+2)xk+1=(∑i=0kxi)n−m−1(1−x)2⋅(1+(k+1)xk+2−(k+2)xk+1)=\frac{(\sum_{i=0}^kx^i)^{n-m-1}}{(1-x)^2}·(1+(k+1)x^{k+2}-(k+2)x^{k+1})=(1−x)2(∑i=0kxi)n−m−1⋅(1+(k+1)xk+2−(k+2)xk+1)
由等比数列求和公式得∑i=0kxi=1−xk+11−x\sum_{i=0}^kx^i=\frac{1-x^{k+1}}{1-x}∑i=0kxi=1−x1−xk+1,F(x)F(x)F(x)继续等价于
(1−xk+1)n−m−1(1−x)n−m−1×(1−x)2⋅(1+(k+1)xk+2−(k+2)xk+1)\frac{(1-x^{k+1})^{n-m-1}}{(1-x)^{n-m-1}\times (1-x)^2}·(1+(k+1)x^{k+2}-(k+2)x^{k+1})(1−x)n−m−1×(1−x)2(1−xk+1)n−m−1⋅(1+(k+1)xk+2−(k+2)xk+1)=(1−xk+1)n−m−1(1−x)n−m+1⋅(1+(k+1)xk+2−(k+2)xk+1)=\frac{(1-x^{k+1})^{n-m-1}}{(1-x)^{n-m+1}}·(1+(k+1)x^{k+2}-(k+2)x^{k+1})=(1−x)n−m+1(1−xk+1)n−m−1⋅(1+(k+1)xk+2−(k+2)xk+1)
(1−xk+1)n−m−1(1-x^{k+1})^{n-m-1}(1−xk+1)n−m−1用二项式定理展开变成∑i=0n−m−1Cn−m−1i(−xk+1)i\sum_{i=0}^{n-m-1} C_{n-m-1}^{i}(-x^{k+1})^i∑i=0n−m−1Cn−m−1i(−xk+1)i
1(1−x)n−m+1=(1−x)−(n−m+1)\frac{1}{(1-x)^{n-m+1}}=(1-x)^{-(n-m+1)}(1−x)n−m+11=(1−x)−(n−m+1)用广义二项式定理展开变成∑r=0∞Cn−m+1rxr\sum_{r=0}^{∞}C_{n-m+1}^{r}x^r∑r=0∞Cn−m+1rxr
第三部分是三个单项式的和,考虑拆开计算,只需要枚举第一部分xxx的指数
#include <cstdio>
#include <cmath>
using namespace std;
#define int long long
#define mod 998244353
#define maxn 1000005
int T, n, m, k, cnt;
int fac[maxn], inv[maxn], phi[maxn], prime[maxn];
bool vis[maxn];int qkpow( int x, int y ) {int ans = 1;while( y ) {if( y & 1 ) ans = ans * x % mod;x = x * x % mod;y >>= 1;}return ans;
}void init( int n ) {fac[0] = inv[0] = 1;for( int i = 1;i <= maxn;i ++ ) fac[i] = fac[i - 1] * i % mod;inv[maxn] = qkpow( fac[maxn], mod - 2 );for( int i = maxn - 1;i;i -- ) inv[i] = inv[i + 1] * ( i + 1 ) % mod;
}int C( int n, int m ) {return fac[n] * inv[m] % mod * inv[n - m] % mod;
}void sieve( int n ) {phi[1] = 1;for( int i = 2;i <= n;i ++ ) {if( ! vis[i] ) prime[++ cnt] = i, phi[i] = i - 1;for( int j = 1;j <= cnt && i * prime[j] <= n;j ++ ) {vis[i * prime[j]] = 1;if( i % prime[j] == 0 ) {phi[i * prime[j]] = phi[i] * prime[j];break;}elsephi[i * prime[j]] = phi[i] * ( prime[j] - 1 );}}
}int gcd( int x, int y ) {if( ! y ) return x;else return gcd( y, x % y );
}int F( int n, int m ) {int ans1 = 0, ans2 = 0, ans3 = 0;for( int i = 0;i * ( k + 1 ) <= m;i ++ ) {int x = i & 1 ? mod - C( n - m - 1, i ) : C( n - m - 1, i );int j = i * ( k + 1 );ans1 = ( ans1 + x * C( n - j, m - j ) % mod ) % mod;if( j + k + 2 <= m ) ans2 = ( ans2 + x * C( n - j - k - 2, m - j - k - 2 ) % mod ) % mod;if( j + k + 1 <= m ) ans3 = ( ans3 + x * C( n - j - k - 1, m - j - k - 1 ) % mod ) % mod; }return ( ans1 + ans2 * ( k + 1 ) % mod - ans3 * ( k + 2 ) % mod + mod ) % mod;
}signed main() {scanf( "%lld", &T );init( maxn - 5 );sieve( maxn - 5 );while( T -- ) {scanf( "%lld %lld %lld", &n, &m, &k );int d = gcd( n, m ), ans = 0, sqt = sqrt( d );for( int i = 1;i <= sqt;i ++ ) {if( d % i == 0 ) {ans = ( ans + F( n / i, m / i ) * phi[i] % mod ) % mod;if( d / i != i ) ans = ( ans + F( n / ( d / i ), m / ( d / i ) ) * phi[d / i] % mod ) % mod;}}printf( "%lld\n", ans * qkpow( n, mod - 2 ) % mod );}return 0;
}