去年不知道干了些啥,什么省选/营题都没做。
现在赶应该还来得及(?)
「PKUWC2018」Minimax
Done 2019.12.04 9:38:55
线段树合并船新玩法???
\(O(n^2)\) 很好想,先把叶子的权值离散化,然后 \(dp[u][i]\) 表示 \(u\) 的权值是 \(i\) 的概率。
没事干了,上线段树合并。(???)
线段树合并新玩法:对于线段树上的一个叶子,比它编号大的所有点在线段树上被拆成的区间应该是:对于递归到这个叶子路上的每个节点,如果是个左儿子,就算上它的兄弟(是个右儿子)。
于是可以通过这个方法线段树合并。往左走的时候,左儿子需要乘上的数应该加上右儿子的和。具体见代码。
时间复杂度 \(O(n\log n)\)。
代码
#include
using namespace std;
typedef long long ll;
const int MAXN=300030,MOD=998244353;
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
template
inline void read(T &x){
x=0;
char ch=getchar();bool f=false;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
if(f) x=-x;
}
int n,cnt[MAXN],ch[MAXN][2],val[MAXN],tmp[MAXN],tl,rt[MAXN],tot,ls[MAXN*50],rs[MAXN*50],sum[MAXN*50],mul[MAXN*50],s[MAXN],ans;
inline int qpow(int a,int b){
int ans=1;
for(;b;b>>=1,a=1ll*a*a%MOD) if(b&1) ans=1ll*ans*a%MOD;
return ans;
}
inline int newnode(){
int x=++tot;
ls[x]=rs[x]=sum[x]=0;
mul[x]=1;
return x;
}
inline void pushup(int x){
sum[x]=(sum[ls[x]]+sum[rs[x]])%MOD;
}
inline void setmul(int x,int v){
if(!x) return;
sum[x]=1ll*sum[x]*v%MOD;
mul[x]=1ll*mul[x]*v%MOD;
}
inline void pushdown(int x){
if(mul[x]!=1){
setmul(ls[x],mul[x]);
setmul(rs[x],mul[x]);
mul[x]=1;
}
}
void update(int &x,int l,int r,int p,int v){
if(!x) x=newnode();
if(l==r) return void(sum[x]=v);
int mid=(l+r)>>1;
pushdown(x);
if(mid>=p) update(ls[x],l,mid,p,v);
if(mid>1,lsx=sum[ls[x]],lsy=sum[ls[y]],rsx=sum[rs[x]],rsy=sum[rs[y]];
ls[x]=merge(ls[x],ls[y],l,mid,(xmul+1ll*rsy%MOD*(1-v+MOD))%MOD,(ymul+1ll*rsx%MOD*(1-v+MOD))%MOD,v);
rs[x]=merge(rs[x],rs[y],mid+1,r,(xmul+1ll*lsy%MOD*v)%MOD,(ymul+1ll*lsx%MOD*v)%MOD,v);
pushup(x);
return x;
}
void out(int x,int l,int r){
if(!x) return;
if(l==r) return void(s[l]=sum[x]);
int mid=(l+r)>>1;
pushdown(x);
out(ls[x],l,mid);
out(rs[x],mid+1,r);
}
void dfs(int u){
if(!cnt[u]) update(rt[u],1,tl,val[u],1);
else if(cnt[u]==1) dfs(ch[u][0]),rt[u]=rt[ch[u][0]];
else dfs(ch[u][0]),dfs(ch[u][1]),rt[u]=merge(rt[ch[u][0]],rt[ch[u][1]],1,tl,0,0,val[u]);
}
int main(){
read(n);
FOR(i,1,n){
int f;
read(f);
if(f) ch[f][cnt[f]++]=i;
}
FOR(i,1,n){
read(val[i]);
if(cnt[i]) val[i]=1ll*val[i]*qpow(10000,MOD-2)%MOD;
else tmp[++tl]=val[i];
}
sort(tmp+1,tmp+tl+1);
FOR(i,1,n) if(!cnt[i]) val[i]=lower_bound(tmp+1,tmp+tl+1,val[i])-tmp;
dfs(1);
out(rt[1],1,tl);
FOR(i,1,tl) ans=(ans+1ll*i*tmp[i]%MOD*s[i]%MOD*s[i])%MOD;
printf("%d\n",ans);
return 0;
}
「PKUWC2018」Slay the Spire
Done 2019.12.05 22:14:53
一道 sb 题想了这么久,身败名裂……
有个很显然的结论:先把强化牌从大到小能出就出(出撑死 \(k-1\) 张),然后就把攻击牌从大到小能出就出。
不妨先把每种牌从大到小排序。
枚举选了 \(i\) 张强化牌,\(m-i\) 张攻击牌。
先求对于所有选强化牌的方案,最后倍数的和。
\(f_{i,j}\) 表示前 \(i\) 张牌中选了 \(j\) 张的倍数和。
然后求对于所有选攻击牌的方案,打出的牌的点数之和。发现打出的攻击牌的张数就是 \(\max(1,k-i)\)。
有个直接的想法是记录 \(g_{i,j,k}\) 表示前 \(i\) 张牌,选了 \(j\) 张牌,打出了 \(k\) 张的总和。
于是我就卡在这个垃圾做法卡了好久。
实际上可以枚举打出的牌中最小的是哪张。假设是 \(j\)。
记录 \(g_{i,j,0/1}\) 表示前 \(i\) 张牌,选了 \(j\) 张牌,第 \(i\) 张牌有没有选的总和。
答案就是 \(f_{n,i}g_{j,\max(1,k-i),1}\binom{n-j}{m-\max(i+1,k)}\)。倍数,总和,和在更小的牌中随便选。
时间复杂度 \(O(\sum n^2)\)。
代码
#include
using namespace std;
typedef long long ll;
const int MAXN=3333,MOD=998244353;
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
template
inline void read(T &x){
x=0;
char ch=getchar();bool f=false;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
if(f) x=-x;
}
int T,n,m,k,a[MAXN],b[MAXN],fac[MAXN],inv[MAXN],invfac[MAXN],f[MAXN][MAXN],g[MAXN][MAXN],cnt[MAXN][MAXN];
inline int C(int n,int m){
if(n());
sort(b+1,b+n+1,greater());
fac[0]=fac[1]=inv[1]=invfac[0]=invfac[1]=1;
FOR(i,2,2*n){
fac[i]=1ll*fac[i-1]*i%MOD;
inv[i]=MOD-1ll*(MOD/i)*inv[MOD%i]%MOD;
invfac[i]=1ll*invfac[i-1]*inv[i]%MOD;
}
f[0][0]=1;
FOR(i,1,n) FOR(j,0,i){
f[i][j]=f[i-1][j];
if(j) f[i][j]=(f[i][j]+1ll*f[i-1][j-1]*(j<=k-1?a[i]:1))%MOD;
}
FOR(i,1,n) FOR(j,0,i){
if(j) cnt[i][j]=g[i][j]=(g[i-1][j-1]+1ll*b[i]*C(i-1,j-1))%MOD;
g[i][j]=(g[i][j]+g[i-1][j])%MOD;
}
int ans=0;
FOR(i,0,m) FOR(j,0,n) ans=(ans+1ll*f[n][i]*cnt[j][max(1,k-i)]%MOD*C(n-j,m-max(i+1,k)))%MOD;
printf("%d\n",ans);
}
return 0;
}
「PKUWC2018」斗地主
咕了。
「PKUWC2018」随机算法
Done 2019.12.06 12:05:08
神仙状压?
\(f_{S,i}\) 表示 \(S\) 中的点要么在求出的独立集中,要么与独立集相邻,求出的独立集大小为 \(i\) 的方案数。这里的方案只考虑 \(S\) 中的点的位置,\(S\) 外的点不影响方案数。
答案为 \(f_{\{1,2,\dots,n\},n}\)。如果第一维不是全集,就可以多放几个点。
转移,随便选一个不在 \(S\) 中的点 \(j\)。预处理与 \(j\) 相邻的点集 \(mask_j\),那么 \(f_{S\cup\{j\}\cup mask_j}\) 会多 \(f_S\times A_{n-|S|-1}^{|S\cup\{j\}\cup mask_j|-|S|-1}\)。
后面那个排列是把多限制的那些点(\(j\) 本身除外,它肯定正好拼在目前第一个空位)塞到剩下的空位,考虑顺序。
这是 \(O(n^22^n)\) 的。
考虑为什么要记 \(i\) 这一位:因为我们最后用到的只有最大的独立集。
但是有个常用的转移方法是:记录另一个 DP 数组表示这个集合的最大独立集。转移到这个集合时,如果是个更大的独立集,那前面的转移都废了。
这样就是 \(O(n2^n)\) 了。
代码
#include
using namespace std;
typedef long long ll;
const int maxn=1048576,mod=998244353;
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
template
inline void read(T &x){
x=0;
char ch=getchar();bool f=false;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
if(f) x=-x;
}
int n,m,S[22],f[maxn],mx[maxn],fac[22],inv[22],invfac[22],sz[maxn];
bool vis[maxn];
inline int A(int n,int m){
if(n<0 || m<0 || n>1]+(i&1);
f[0]=1;vis[0]=1;
FOR(i,0,(1<>j)&1)){
int to=i|(1<
「PKUWC2018」猎人杀
Done 2019.12.09 21:44:02
一道比一道神……
首先发现被杀死的猎人和杀手是哪个无关,可以就看成每次你自己按权随机。
发现很难算,因为每次的分母不一样。
那么考虑一种新的随机:在所有人中随机(包括死的),如果随机到一个死人就重新随机,直到随机到一个活人为止,然后把他毙了。
这样是等价的。
证明,考虑所有人的 \(w\) 之和为 \(all\),死人的 \(w\) 之和为 \(kill\),那么原来每个活人被随机到的概率是 \(\frac{w}{all-kill}\)。
枚举随机到多少次死人,现在每个活人被随机到的概率为 \(\sum\limits_{i=0}^{+\infty}(\frac{kill}{all})^i\frac{w}{all}=\frac{w}{all}\times\frac{1}{1-\frac{kill}{all}}=\frac{w}{all-kill}\)。
现在考虑容斥(???),枚举 \(S\) 这个集合中的所有人都在 \(1\) 之前被杀死,剩下的人任意。
枚举第一次随机 \(1\) 之前随机了几次(就是 \(1\) 什么时候被杀),\(\sum\limits_{i=0}^{+\infty}(\frac{all-w_1-sum(S)}{all})^i\frac{w_1}{all}=\frac{w_1}{all}\times\frac{1}{1-\frac{all-w_1-sum(S)}{all}}=\frac{w_1}{w_1+sum(S)}\)。
所以答案就是 \(\sum\limits_{S\subseteq \{2,3,\dots n\}}(-1)^{|S|}\frac{w_1}{w_1+sum(S)}\)。
考虑枚举 \(sum(S)\),计算所有和为 \(i\) 的集合 \(S\) 的 \((-1)^{|S|}\) 之和。
对每个除 \(1\) 以外的人构造多项式 \(1-x^{w_i}\),那么和为 \(i\) 的集合就是所有多项式乘积的第 \(i\) 项。
求所有的乘积可以用分治乘起来。时间复杂度是 \(O(\sum\text{每个节点的多项式次数}\log\text{每个节点的多项式次数})\)。
把 \(\log\) 放松一点变成 \(\log\sum w_i\),然后就是 \(O((\sum\text{每个节点的多项式次数})\log\sum w_i)\)。
每个叶子节点都会对它和它所有祖先的多项式次数产生贡献。这一共有 \(O(\log n)\) 个,所以复杂度为 \(O((\sum w_i)\log(\sum w_i)\log n)\)。
代码
#include
using namespace std;
typedef long long ll;
const int maxn=266666,mod=998244353;
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
template
inline void read(T &x){
x=0;
char ch=getchar();bool f=false;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
if(f) x=-x;
}
int n,w[maxn],sum[maxn],lim,l,rev[maxn],ans;
vector A[maxn];
inline void init(int upr){
for(lim=1,l=0;lim>1]>>1)|((i&1)<<(l-1));
}
inline int qpow(int a,int b){
int ans=1;
for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) ans=1ll*ans*a%mod;
return ans;
}
void NTT(vector &A,int tp){
while(A.size()>1;
solve(o<<1,l,mid);
solve(o<<1|1,mid+1,r);
init(sum[r]-sum[l-1]+1);
NTT(A[o<<1],1);NTT(A[o<<1|1],1);
FOR(i,0,lim-1) A[o].push_back(1ll*A[o<<1][i]*A[o<<1|1][i]%mod);
NTT(A[o],-1);
}
int main(){
read(n);
FOR(i,1,n) read(w[i]),sum[i]=sum[i-1]+w[i];
solve(1,2,n);
FOR(i,0,sum[n]-sum[1]) ans=(ans+1ll*w[1]*qpow((w[1]+i)%mod,mod-2)%mod*A[1][i])%mod;
printf("%d\n",ans);
return 0;
}
「PKUWC2018」随机游走
Done 2019.12.05 18:16:37
咋就可做题了……咋就套路题了……
显然的 \(O(Q+8^nn^3)\) 暴力高斯消元就略过了。
对于“所有都选到至少一次”这种问题,先上个 min-max 容斥。(被教做人*1)
然后就是要对每个点集都求出:从根开始,第一次走到点集中的点的期望步数。求完之后很容易能高维前缀和(FMT)\(O(n2^n)\) 地求出每个点集的最终答案。
对每个点集暴力高斯消元,复杂度 \(O(2^nn^3)\)。
然后发现是树上高斯消元,就可以直接 DP 了。(被教做人*2)
设 \(f_u\) 表示从 \(u\) 开始第一次走到点集中的点的期望步数。再设 \(a_u\) 和 \(b_u\) 满足 \(f_u=a_uf_{fa_u}+b_u\)。最终的期望步数为 \(b_{rt}\)。
从意义出发,\(f_u=\frac{1}{deg_u}(f_{fa_u}+\sum\limits_{v\in son_u} f_v)\)。
把 \(f_v=a_vf_u+b_v\) 代入得:\(f_u=\frac{1}{deg_u}(f_{fa_u}+\sum\limits_{v\in son_u}(a_vf_u+b_v))\)。
整理一波:\(a_u=\frac{1}{deg_u-\sum\limits_{v\in son_u}a_v},b_u=\frac{deg_u+\sum\limits_{v\in son_u}b_v}{deg_u-\sum\limits_{v\in son_u}a_v}\)。
时间复杂度 \(O(Q+n2^n\log)\)。这个 \(\log\) 是求逆元,可以省掉,但是懒得写了。
代码
#include
using namespace std;
typedef long long ll;
const int MAXN=262144,mod=998244353;
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
template
inline void read(T &x){
x=0;
char ch=getchar();bool f=false;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
if(f) x=-x;
}
int n,q,rt,el,head[22],to[44],nxt[44],deg[22],a[22],b[22],s[MAXN],sz[MAXN];
inline void add(int u,int v){
to[++el]=v;nxt[el]=head[u];head[u]=el;
}
inline int qpow(int a,int b){
int ans=1;
for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) ans=1ll*ans*a%mod;
return ans;
}
void dfs(int S,int u,int f){
int asum=0,bsum=0;
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(v==f) continue;
dfs(S,v,u);
asum=(asum+a[v])%mod;
bsum=(bsum+b[v])%mod;
}
if((S>>u)&1) a[u]=b[u]=0;
else{
int inv=qpow((deg[u]-asum+mod)%mod,mod-2);
a[u]=inv,b[u]=1ll*inv*(deg[u]+bsum)%mod;
}
}
int main(){
read(n);read(q);read(rt);rt--;
FOR(i,1,n-1){
int u,v;
read(u);read(v);
u--;v--;
add(u,v);add(v,u);
deg[u]++;deg[v]++;
}
FOR(i,1,(1<>1]+(i&1);
if(sz[i]%2==0) s[i]=(mod-s[i])%mod;
}
for(int i=1;i<1<
「PKUSC2018」真实排名
Done 2019.12.04 15:07:54
在我说底下「神仙的游戏」是全场最可做题之后就被喷了,滚过来看这题……
然而感觉 lower_bound 和 upper_bound 的细节在考场上也八成会暴毙……
不过这题想起来还是很简单的。
直接分类:
- 特判 \(a_i=0\),此时任选即可,答案为 \(\binom{n}{k}\)。
- 如果 \(i\) 没有翻倍,那么如果 \([\lfloor\frac{a_i+1}{2}\rfloor,a_i-1]\) 这里面有人翻倍了,他就绝杀 \(i\) 了。所以在剩下的人中再选 \(k\) 个。
- 如果 \(i\) 翻倍了,那么他就多踩了 \([a_i,2a_i-1]\) 这些人。所以这些人也必须翻倍。剩下的可以任选。
时间复杂度 \(O(n\log n)\),瓶颈在排序和二分。
代码
#include
using namespace std;
typedef long long ll;
const int MAXN=100010,mod=998244353;
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
template
inline void read(T &x){
x=0;
char ch=getchar();bool f=false;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
if(f) x=-x;
}
int n,k,a[MAXN],b[MAXN],fac[MAXN],inv[MAXN],invfac[MAXN];
inline int cnt(int x){
int p=upper_bound(b+1,b+n+1,x)-b;
if(p>n || b[p]>x) p--;
return p;
}
inline int cnt(int l,int r){return cnt(r)-cnt(l-1);}
inline int C(int n,int m){
if(n
「PKUSC2018」最大前缀和
Done 2019.05.24 22:24:28
神仙状压。
之前做过
「PKUSC2018」主斗地
咕了。
「PKUSC2018」星际穿越
Done 2019.12.09 9:45:49
神仙套路?
令 \(sum(x,y)\) 表示从 \(y\) 跳到 \(x\) 到 \(y\) 中每一个点的距离之和。答案为 \(sum(l,x)-sum(r+1,x)\)。
\(y\) 第一步能跳到的最小点是 \(l_y\),最大点是满足 \(l_k\le y\) 的最大的 \(k\)。
那么第二步能跳到的最小点是 \(\min\limits_{i=l_y}^nl_i\)。
为什么上界是 \(n\),不需要考虑 \(l_i\le y\) 这个条件?因为对于 \(l_i>y\) 的 \(i\),肯定不是最优的,比 \(y\) 都要劣。
发现从第二步开始,就不会往右跳了。因为往右跳可能更优肯定是因为它的 \(l\) 比当前所在点的 \(l\) 更小。但是这样的话,它的 \(l\) 肯定比 \(y\) 小,第一步往右跳就能跳到这个点。
发现如果能跳到的最小点是 \(a\),那么 \([a,y)\) 都能在这么多步以内跳到。
所以如果第 \(i\) 步(注意 \(i\ne 0\),因为一开始不能在 \(y\) 右边)能跳到的最小点是 \(a\),那么第 \(i+1\) 步能跳到的最小点是 \(\min\limits_{i=a}^nl_i\)。
没事干了,上倍增。(???)
令 \(f_{i,j}\) 表示从 \(i\) 开始跳 \(2^j\) 步最小编号的点(注意这里为了方便,假设 \(i\) 右边的点一开始就能用),\(g_{i,j}\) 表示从 \(i\) 跳到 \(f_{i,j}\) 到 \(i\) 的距离之和。
那么有 \(f_{i,0}\) 为 \(l_i\) 的后缀最小值,\(g_{i,0}=i-f_{i,0}\)。
转移,\(f_{i,j}=f_{f_{i,j-1},j-1},g_{i,j}=g_{i,j-1}+g_{f_{i,j-1},j-1}+2^{j-1}(f_{i,j-1}-f_{i,j})\)。\(g\) 的转移最后一部分是因为 \([f_{i,j},f_{i,j-1})\) 在跳到 \(f_{i,j-1}\) 后(这部分的和是 \(g_{f_{i,j-1},j-1}\))还要再花 \(2^{j-1}\) 才能到 \(i\)。
倍增的过程,从 \(y\) 开始跳,特殊处理第一步(上面的 \(f\) 没有这个特殊情况),然后从大到小看跳了 \(2^i\) 步是否仍然不在 \(x\) 左边。同时记一下贡献。具体过程与上面类似,详见代码。
时间复杂度 \(O((n+q)\log n)\)。
代码
#include
using namespace std;
typedef long long ll;
const int maxn=333333;
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
template
inline void read(T &x){
x=0;
char ch=getchar();bool f=false;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
if(f) x=-x;
}
int n,lt,l[maxn],q,f[20][maxn];
ll g[20][maxn];
ll gcd(ll x,ll y){return y?gcd(y,x%y):x;}
ll sum(int x,int y){
if(l[y]<=x) return y-x;
ll s=y-l[y];
int at=l[y],cnt=1;
ROF(i,lt,0) if(f[i][at]>=x){
s+=1ll*cnt*(at-f[i][at])+g[i][at];
at=f[i][at];
cnt+=1<x) s+=1ll*cnt*(at-x)+at-x;
return s;
}
int main(){
read(n);
FOR(i,2,n) read(l[i]);
lt=log(n)/log(2);
f[0][n]=l[n];
ROF(i,n-1,1) f[0][i]=min(f[0][i+1],l[i]);
FOR(i,1,n) g[0][i]=i-f[0][i];
FOR(j,1,lt) FOR(i,1,n){
f[j][i]=f[j-1][f[j-1][i]];
g[j][i]=g[j-1][i]+g[j-1][f[j-1][i]]+(1ll<<(j-1))*(f[j-1][i]-f[j][i]);
}
read(q);
while(q--){
int l,r,x;
read(l);read(r);read(x);
ll s=sum(l,x)-sum(r+1,x),cnt=r-l+1,g=gcd(s,cnt);
s/=g;cnt/=g;
printf("%lld/%lld\n",s,cnt);
}
return 0;
}
「PKUSC2018」神仙的游戏
Done 2019.12.04 12:04:00
或成最可做题???
不知道大家为什么说 minimax 最可做,这题我明明很快就切了虽然还是先想了个假做法
考虑有长度为 \(i\) 的 border 就是有长度为 \(n-i\) 的循环节。
我们判断 \(i\) 合不合法的时候,发现对于所有模 \(n-i\) 相同的位,上面不能同时有 0 和 1。
转换一下,枚举一个是 0 的位置 \(x\),是 1 的位置 \(y\),那么 \(|x-y|\) 及其约数都不能作为循环节的长度。
不妨先求出所有 \(|x-y|\) 的可能值,最后枚举倍数。
那么就能随便 FFT 解决了。时间复杂度 \(O(n\log n)\)。
FFT 太慢了……NTT 会快很多,但是没写。
别像我第一次一样沙雕,只需要一次 FFT。
代码
#include
using namespace std;
typedef long long ll;
const int MAXN=1111111;
const double pi=3.14159265358;
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
template
inline void read(T &x){
x=0;
char ch=getchar();bool f=false;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
if(f) x=-x;
}
struct comp{
double x,y;
comp(double xx=0,double yy=0):x(xx),y(yy){}
comp operator+(const comp &c)const{return comp(x+c.x,y+c.y);}
comp operator-(const comp &c)const{return comp(x-c.x,y-c.y);}
comp operator*(const comp &c)const{return comp(x*c.x-y*c.y,x*c.y+y*c.x);}
}A[MAXN],B[MAXN];
int n,a[MAXN],b[MAXN],lim,l,rev[MAXN];
ll ans;
double res[MAXN];
char s[MAXN];
void fft(comp *A,int tp){
FOR(i,0,lim-1) if(i>1]>>1)|((i&1)<<(l-1));
FOR(i,0,n-1){
if(s[i]=='0') A[i]=comp(1,0);
if(s[i]=='1') B[n-1-i]=comp(1,0);
}
fft(A,1);fft(B,1);
FOR(i,0,lim-1) A[i]=A[i]*B[i];
fft(A,-1);
FOR(i,0,n-1) res[i]+=A[n-1+i].x+A[n-1-i].x;
ROF(i,n,1) FOR(j,2,n/i) res[i]+=res[i*j];
FOR(i,1,n) if(fabs(res[n-i])<1e-3) ans^=1ll*i*i;
printf("%lld\n",ans);
return 0;
}
「PKUSC2018」PKUSC
咕了。
可以转换成对于每个敌人,落在多边形内部的概率。
多边形旋转和敌人旋转没啥区别,转换成敌人转一圈落在多边形内部的概率。
轨迹是个圆,这个圆上有一堆圆弧在多边形内部。转换成求这些圆弧所对圆心角的和。
这个大概求出所有交点,极角序排序搞一搞。
说的容易,但是调了一天了,现在还咕着。