去年不知道干了些啥,什么省选/营题都没做。

现在赶应该还来得及(?)

「PKUWC2018」Minimax

Done 2019.12.04 9:38:55

线段树合并船新玩法???

\(O(n^2)\) 很好想,先把叶子的权值离散化,然后 \(dp[u][i]\) 表示 \(u\) 的权值是 \(i\) 的概率。

没事干了,上线段树合并。(???)

线段树合并新玩法:对于线段树上的一个叶子,比它编号大的所有点在线段树上被拆成的区间应该是:对于递归到这个叶子路上的每个节点,如果是个左儿子,就算上它的兄弟(是个右儿子)。

于是可以通过这个方法线段树合并。往左走的时候,左儿子需要乘上的数应该加上右儿子的和。具体见代码。

时间复杂度 \(O(n\log n)\)。

#include<bits/stdc++.h>
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<typename T>
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<p) update(rs[x],mid+1,r,p,v);
pushup(x);
}
int merge(int &x,int y,int l,int r,int xmul,int ymul,int v){
if(!x && !y) return 0;
if(!x){setmul(y,ymul);return y;}
if(!y){setmul(x,xmul);return x;}
pushdown(x);pushdown(y);
int mid=(l+r)>>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<bits/stdc++.h>
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<typename T>
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<m || n<0 || m<0) return 0;
return 1ll*fac[n]*invfac[m]%MOD*invfac[n-m]%MOD;
}
int main(){
read(T);
while(T--){
read(n);read(m);read(k);
FOR(i,1,n) read(a[i]);
FOR(i,1,n) read(b[i]);
sort(a+1,a+n+1,greater<int>());
sort(b+1,b+n+1,greater<int>());
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<bits/stdc++.h>
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<typename T>
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<m) return 0;
return 1ll*fac[n]*invfac[n-m]%mod;
}
int main(){
read(n);read(m);
FOR(i,1,m){
int u,v;
read(u);read(v);
u--;v--;
S[u]|=1<<v;S[v]|=1<<u;
}
fac[0]=fac[1]=inv[1]=invfac[0]=invfac[1]=1;
FOR(i,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;
}
FOR(i,1,(1<<n)-1) sz[i]=sz[i>>1]+(i&1);
f[0]=1;vis[0]=1;
FOR(i,0,(1<<n)-1){
if(!vis[i]) continue;
FOR(j,0,n-1) if(!((i>>j)&1)){
int to=i|(1<<j)|S[j];
vis[to]=true;
if(mx[to]<mx[i]+1) mx[to]=mx[i]+1,f[to]=0;
if(mx[to]==mx[i]+1) f[to]=(f[to]+1ll*f[i]*A(n-sz[i]-1,sz[to]-sz[i]-1))%mod;
}
}
printf("%d\n",int(1ll*f[(1<<n)-1]*invfac[n]%mod));
return 0;
}

「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<bits/stdc++.h>
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<typename T>
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<int> A[maxn];
inline void init(int upr){
for(lim=1,l=0;lim<upr;lim<<=1,l++);
FOR(i,0,lim-1) rev[i]=(rev[i>>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<int> &A,int tp){
while(A.size()<lim) A.push_back(0);
FOR(i,0,lim-1) if(i<rev[i]) swap(A[i],A[rev[i]]);
for(int i=1;i<lim;i<<=1){
int Wn=qpow(3,mod-1+tp*(mod-1)/(i<<1));
for(int j=0;j<lim;j+=i<<1){
int w=1;
FOR(k,0,i-1){
int x=A[j+k],y=1ll*A[i+j+k]*w%mod;
A[j+k]=(x+y)%mod;A[i+j+k]=(x-y+mod)%mod;
w=1ll*w*Wn%mod;
}
}
}
if(tp==-1){
int linv=qpow(lim,mod-2);
FOR(i,0,lim-1) A[i]=1ll*A[i]*linv%mod;
}
}
void solve(int o,int l,int r){
if(l==r){
init(w[l]+1);
FOR(i,1,lim) A[o].push_back(0);
A[o][0]=1;
A[o][w[l]]=mod-1;
return;
}
int mid=(l+r)>>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<bits/stdc++.h>
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<typename T>
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<<n)-1){
MEM(a,0);MEM(b,0);
dfs(i,rt,-1);
s[i]=b[rt];
sz[i]=sz[i>>1]+(i&1);
if(sz[i]%2==0) s[i]=(mod-s[i])%mod;
}
for(int i=1;i<1<<n;i<<=1)
for(int j=0;j<1<<n;j+=i<<1)
FOR(k,0,i-1) s[i+j+k]=(s[i+j+k]+s[j+k])%mod;
while(q--){
int k,S=0;
read(k);
while(k--){
int x;
read(x);
S|=1<<(x-1);
}
printf("%d\n",s[S]);
}
return 0;
}

「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<bits/stdc++.h>
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<typename T>
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<m || n<0 || m<0) return 0;
return 1ll*fac[n]*invfac[m]%mod*invfac[n-m]%mod;
}
int main(){
read(n);read(k);
FOR(i,1,n) read(a[i]),b[i]=a[i];
sort(b+1,b+n+1);
fac[0]=fac[1]=inv[1]=invfac[0]=invfac[1]=1;
FOR(i,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;
}
FOR(i,1,n){
if(!a[i]){
printf("%d\n",C(n,k));
continue;
}
int s1=C(cnt((a[i]-1)/2)+cnt(a[i],1e9)-1,k);
int s2=C(cnt(a[i]-1)+cnt(2*a[i],1e9),k-cnt(a[i],2*a[i]-1));
printf("%d\n",(s1+s2)%mod);
}
return 0;
}

「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<bits/stdc++.h>
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<typename T>
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<<i;
}
if(at>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<bits/stdc++.h>
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<typename T>
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<rev[i]) swap(A[i],A[rev[i]]);
for(int i=1;i<lim;i<<=1){
comp Wn(cos(pi/i),tp*sin(pi/i));
for(int j=0;j<lim;j+=i<<1){
comp w(1,0);
FOR(k,0,i-1){
comp x=A[j+k],y=A[i+j+k]*w;
A[j+k]=x+y;A[i+j+k]=x-y;
w=w*Wn;
}
}
}
if(tp==-1) FOR(i,0,lim-1) A[i]=comp(A[i].x/lim,0);
}
int main(){
scanf("%s",s);
n=strlen(s);
for(lim=1,l=0;lim<n*2;lim<<=1,l++);
FOR(i,0,lim-1) rev[i]=(rev[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

咕了。

可以转换成对于每个敌人,落在多边形内部的概率。

多边形旋转和敌人旋转没啥区别,转换成敌人转一圈落在多边形内部的概率。

轨迹是个圆,这个圆上有一堆圆弧在多边形内部。转换成求这些圆弧所对圆心角的和。

这个大概求出所有交点,极角序排序搞一搞。

说的容易,但是调了一天了,现在还咕着。

PKUWC/SC 做题笔记的更多相关文章

  1. C语言程序设计做题笔记之C语言基础知识(下)

    C 语言是一种功能强大.简洁的计算机语言,通过它可以编写程序,指挥计算机完成指定的任务.我们可以利用C语言创建程序(即一组指令),并让计算机依指令行 事.并且C是相当灵活的,用于执行计算机程序能完成的 ...

  2. C语言程序设计做题笔记之C语言基础知识(上)

    C语言是一种功能强大.简洁的计算机语言,通过它可以编写程序,指挥计算机完成指定的任务.我们可以利用C语言创建程序(即一组指令),并让计算机依指令行事.并且C是相当灵活的,用于执行计算机程序能完成的几乎 ...

  3. SDOI2017 R1做题笔记

    SDOI2017 R1做题笔记 梦想还是要有的,万一哪天就做完了呢? 也就是说现在还没做完. 哈哈哈我竟然做完了-2019.3.29 20:30

  4. SDOI2014 R1做题笔记

    SDOI2014 R1做题笔记 经过很久很久的时间,shzr又做完了SDOI2014一轮的题目. 但是我不想写做题笔记(

  5. SDOI2016 R1做题笔记

    SDOI2016 R1做题笔记 经过很久很久的时间,shzr终于做完了SDOI2016一轮的题目. 其实没想到竟然是2016年的题目先做完,因为14年的六个题很早就做了四个了,但是后两个有点开不动.. ...

  6. LCT做题笔记

    最近几天打算认真复习LCT,毕竟以前只会板子.正好也可以学点新的用法,这里就用来写做题笔记吧.这个分类比较混乱,主要看感觉,不一定对: 维护森林的LCT 就是最普通,最一般那种的LCT啦.这类题目往往 ...

  7. java做题笔记

    java做题笔记 1. 初始化过程是这样的: 1.首先,初始化父类中的静态成员变量和静态代码块,按照在程序中出现的顺序初始化: 2.然后,初始化子类中的静态成员变量和静态代码块,按照在程序中出现的顺序 ...

  8. SAM 做题笔记(各种技巧,持续更新,SA)

    SAM 感性瞎扯. 这里是 SAM 做题笔记. 本来是在一篇随笔里面,然后 Latex 太多加载不过来就分成了两篇. 标 * 的是推荐一做的题目. trick 是我总结的技巧. I. P3804 [模 ...

  9. POI做题笔记

    POI2011 Conspiracy (2-SAT) Description \(n\leq 5000\) Solution 发现可拆点然后使用2-SAT做,由于特殊的关系,可以证明每次只能交换两个集 ...

随机推荐

  1. 十分钟 CODING DevOps 全链路体验

    近期 CODING 团队在 2019 KubeCon 大会上发布 DevOps 一站式解决方案:CODING 2.0.此次 CODING 全新上线了持续集成与制品库模块,通过自动化与标准化的方式来帮助 ...

  2. ORA-27468: ""."" is locked by another process

    You have a scheduler job that generated an error. When the error occurred, you attempted to disable ...

  3. November 10th, Week 45th, Sunday, 2019

    Perfection has no place in love. 爱从不完美. Perfection has no place in love, and we should always try to ...

  4. tcp客户端程序开发

    https://www.cnblogs.com/python-No/ 话不多说,直接进入正题 一:客户端一共分为5大块: 1.创建客户端套接字 2.和服务端套接字建立连接 3.发送数据 4.接收发送 ...

  5. 32(1).层次聚类---AGNES

    层次聚类hierarchical clustering 试图在不同层次上对数据集进行划分,从而形成树形的聚类结构. 一. AGNES AGglomerative NESting:AGNES是一种常用的 ...

  6. 09. Go 语言并发

    Go 语言并发 并发指在同一时间内可以执行多个任务.并发编程含义比较广泛,包含多线程编程.多进程编程及分布式程序等.本章讲解的并发含义属于多线程编程. Go 语言通过编译器运行时(runtime),从 ...

  7. TYUT程序设计入门第四讲练习题题解--数论入门

    程序设计入门第四讲练习题题解--数论入门 对于新知识点的学习,需要不断地刷题训练,才能有所收获,才能更好地消化知识点. 题组链接: 程序设计入门第四讲练习题--数论 by vjudge 题解: A. ...

  8. Notepad++ 异常崩溃 未保存的new *文件列表没了怎么办?

    今天就遇到这种问题了,把之前写的临时代码拷贝到Notepad++,不知道啥时候脑袋一抽风强迫症犯了就把所有临时代码给未保存关闭了,然后懊恼不已,百度了一下解决办法,一下就搜到了. Notepad++是 ...

  9. [考试反思]1112csp-s模拟测试111:二重

    还是AK场.考前信心赛? 而且T3的部分分还放反了所有80的都其实只有50. 总算在AK场真正AK了一次... 手感好,整场考试很顺利.要不是因为T3是原题可能就没这么好看了. 20minT1,50m ...

  10. Ubuntu18.04 设置开机进入命令行模式

    首先来了解下启动级别(Runlevel): 指 Unix 或 类 Unix 操作系统下不同的运行模式,运行级别通常分为 7 级: 运行级别 0:系统停机状态,系统默认运行级别不能设为0,否则不能正常启 ...