ZJOI2020 部分题解
40+100+100 + 30+100+10
Day1T1
- 现在只会 \(40pts\)。
- 首先记录原串所有前缀的双 \(hash\) 值。
- 然后,求出所有相同 本质相同的 ,符合条件的 子串,一起考虑它们对询问的答案。
- 对于这些串,如果它们所在的位置分别为 \([l_1,r_1],[l_2,r_2],...,[l_k,r_k]\)。
- 我们把询问当做一个矩阵,贡献就是矩形加。\((1,l_1)-(r_1,n),(l_1+1,l_2)-(r_2,n),...,(l_{k-1}+1,l_k)-(r_k,n)\) 这些矩形都 \(+1\) 即可。
- 这个可以差分前缀和,时间复杂度有关本质不同的子串个数(感觉为 \(O(n)\))。
- 故总时间复杂度为 \(O(n^2)\)。
#include<cstdio>
#include<map>
#include<vector>
using namespace std;
const int Mod1=1e9+7;
const int Mod2=1e9+9;
typedef long long ll;
typedef pair<int,int> pii;
int n,q; ll a[5100][5100];
ll hash1[210000],hash2[210000];
ll pow1[210000],pow2[210000];
char s[210000];
map<pii,int> mp;
vector<int> vec[210000];
inline ll hs1(int l,int r){
return (hash1[r]-hash1[l-1]*pow1[r-l+1]%Mod1+Mod1)%Mod1;
}
inline ll hs2(int l,int r){
return (hash2[r]-hash2[l-1]*pow2[r-l+1]%Mod2+Mod2)%Mod2;
}
int main(){
scanf("%d%d",&n,&q);
scanf("%s",s+1);
pow1[0]=1; pow2[0]=1;
for (int i=1;i<=n;i++) pow1[i]=pow1[i-1]*3%Mod1;
for (int i=1;i<=n;i++) pow2[i]=pow2[i-1]*3%Mod2;
for (int i=1;i<=n;i++) hash1[i]=(hash1[i-1]*3+(s[i]-'a'+1))%Mod1;
for (int i=1;i<=n;i++) hash2[i]=(hash2[i-1]*3+(s[i]-'a'+1))%Mod2;
for (int mid=1;mid*2<=n;mid++){
mp.clear(); int cnt=0;
for (int i=1;i+mid*2-1<=n;i++){
int x1,x2,y1,y2;
x1=hs1(i,i+mid-1); y1=hs1(i+mid,i+mid*2-1);
x2=hs2(i,i+mid-1); y2=hs2(i+mid,i+mid*2-1);
if (x1==y1&&x2==y2){
if (!mp[pii(x1,x2)]){ mp[pii(x1,x2)]=++cnt; vec[cnt].push_back(0);}
vec[mp[pii(x1,x2)]].push_back(i);
}
}
for (int i=1;i<=cnt;i++){
for (int j=1;j<(int)vec[i].size();j++){
a[vec[i][j-1]+1][vec[i][j]+mid*2-1]++;
a[vec[i][j]+1][vec[i][j]+mid*2-1]--;
}
vec[i].clear();
}
}
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
int x,y;
while (q--){
scanf("%d%d",&x,&y);
printf("%lld\n",a[x][y]);
}
return 0;
}
Day1T2
-
思路类似于去年的那道线段树,就是分成 \(5\) 类点来分别算贡献。(参考了pinkrabbit的 \(blog\):https://www.cnblogs.com/PinkRabbit/p/ZJOI2019D1T2.html)
-
设当前区间为 \([l,r]\) ,父区间为 \([fa_l,fa_r]\)。
-
令 \(tot_i\) 表示第 \(i\) 类点的个数,那么:
tot1=seg_cnt-l*(n-r+1)-(getcnt(l-1)+getcnt(n-r)); if (l==fa_l) tot2=l*(fa_r-r); else tot2=(l-fa_l)*(n-r+1); if (l==fa_l) tot3=getsum(n-fa_r+1,n-(r+1)+1); else tot3=getsum(fa_l,l-1); tot4=l*(n-r+1)-tot2; tot5=getcnt(l-1)+getcnt(n-r)-tot3;
-
其中,\(getcnt(x)\) 是 \(x(x+1)/2\) ,\(getsum(l,r)\) 是 \(l+(l+1)+\cdots+r\)。\(seg_{cnt}=getcnt(n)\)。
-
用同样的 \(f,g\) 的定义,一次操作后,有:\(<f[u],g[u]>\ =\ <\frac{tot_1*0+tot_2*1+tot_3*g[u]+tot_4*f[u]+tot_5*f[u]}{seg_{cnt}},\frac{tot_1*0+tot_2*1+tot_3*g[u]+tot_4*1+tot_5g[u]}{seg_{cnt}}>\)
-
设 \(f[u]=af[u]+bg[u]+c,g[u]=xg[u]+y\) ,一个 \(3*3\) 的矩阵即可。时间复杂度 \(O(n \log K)\)。为了卡常可以把矩乘展开。(没加快读)
#include<cstdio>
using namespace std;
typedef long long ll;
const int Mod=998244353;
int n,k,ans; ll seg_cnt,inv;
ll tot1,tot2,tot3,tot4,tot5;
ll a,b,c,x,y;
inline ll getcnt(int n){ return (1ll*n*(n+1)/2)%Mod;}
inline ll getsum(int x,int y){ return (1ll*(x+y)*(y-x+1)/2)%Mod;}
ll getinv(int x){
if (x==1) return 1;
return 1ll*(Mod-Mod/x)*getinv(Mod%x)%Mod;
}
struct mat{
ll m11,m12,m13,m21,m22,m23,m31,m32,m33;
mat(){ m11=m12=m13=m21=m22=m23=m31=m32=m33=0;}
mat(ll v11,ll v12,ll v13,ll v21,ll v22,ll v23,ll v31,ll v32,ll v33){
m11=v11; m21=v21; m31=v31;
m12=v12; m22=v22; m32=v32;
m13=v13; m23=v23; m33=v33;
}
void operator*=(const mat &x){
ll t1,t2;
t1=(m11*x.m11+m12*x.m21+m13*x.m31)%Mod;
t2=(m11*x.m12+m12*x.m22+m13*x.m32)%Mod;
m13=(m11*x.m13+m12*x.m23+m13*x.m33)%Mod;
m11=t1; m12=t2;
t1=(m21*x.m11+m22*x.m21+m23*x.m31)%Mod;
t2=(m21*x.m12+m22*x.m22+m23*x.m32)%Mod;
m23=(m21*x.m13+m22*x.m23+m23*x.m33)%Mod;
m21=t1; m22=t2;
t1=(m31*x.m11+m32*x.m21+m33*x.m31)%Mod;
t2=(m31*x.m12+m32*x.m22+m33*x.m32)%Mod;
m33=(m31*x.m13+m32*x.m23+m33*x.m33)%Mod;
m31=t1; m32=t2;
}
};
int qpow(){
int a=k;
mat res(1,0,0,0,1,0,0,0,1);
mat x(::a,::b,::c,0,::x,::y,0,0,1);
while (a){
if (a&1) res*=x;
x*=x; a>>=1;
}
return res.m13;
}
int getans(){
tot1%=Mod; tot2%=Mod; tot3%=Mod;
tot4%=Mod; tot5%=Mod;
a=tot4+tot5; b=tot3; c=tot2;
x=tot3+tot5; y=tot2+tot4;
a=a*inv%Mod; b=b*inv%Mod; c=c*inv%Mod; //f[i][u]=a*f[i-1][u]+b*g[i-1][u]+c
x=x*inv%Mod; y=y*inv%Mod; //g[i][u]=x*g[i-1][u]+y
return qpow();
}
void calc(int l,int r,int fa_l,int fa_r){
tot1=seg_cnt-1ll*l*(n-r+1)-(getcnt(l-1)+getcnt(n-r));
if (l==fa_l) tot2=1ll*l*(fa_r-r);
else tot2=1ll*(l-fa_l)*(n-r+1);
if (l==fa_l) tot3=getsum(n-fa_r+1,n-(r+1)+1);
else tot3=getsum(fa_l,l-1);
tot4=1ll*l*(n-r+1)-tot2;
tot5=getcnt(l-1)+getcnt(n-r)-tot3;
ans=(ans+getans())%Mod;
}
void solve(int l,int r){
if (l==r) return;
int mid;
scanf("%d",&mid);
solve(l,mid); solve(mid+1,r);
calc(l,mid,l,r); calc(mid+1,r,l,r);
}
int main(){
scanf("%d%d",&n,&k);
seg_cnt=getcnt(n); inv=getinv(seg_cnt);
tot1=seg_cnt-1; tot2=1; tot3=0; tot4=0; tot5=0; ans=getans();
solve(1,n);
printf("%d\n",ans);
return 0;
}
Day1T3
- 首先有一个结论:如果只有第一种操作,那么答案为 \(\frac{\sum\mid a_i-a_{i-1}\mid}{2}\)(NOIP2018D1T1)。在序列最前面和最后面都添2个0。
- 然后,我们设 \(b_i\) 为第 \(i\) 个位置做了几次 \(2/3\) 操作,\(c_i\) 为第 \(i\) 个位置做了几次 \(1\) 操作。同样在序列最前面和最后面都添2个0。显然,\(a_i=b_i+c_i\)。
- 那么,我们可以发现:若 \(b_{i-2}+c_{i-1}\geqslant a_i\),那么 \(b_{i-2}\geqslant b_i,c_{i-1}\geqslant c_i\)。反之亦然。
- 证明可以考虑:假设 \(b_{i-2}> b_i,c_{i-1}<c_i\),那么将 \(b_i--,c_i++\) 答案不会变劣,其它情况类似。
- 那么我们可以考虑贪心:
- 从左到右考虑每个位置 \(i\)。考虑 \(i\) 的时候确定 \(i-1\)。
- 首先,我们先使 \(b_i=b_{i-2},c_i=c_{i-1}\)。
- 我们记 \(res[i]\) 表示当前的贪心得到的 \(b_i+c_i\) 与 \(a_i\) 的差值。(即 \(a_i-(b_i+c_i)\))。
- 此时,我们要确定 \(i-1\)。
- 若 \(res[i]>0,res[i-1]>0\),那么,我们可以将 \(c_i++,c_{i-1}++\)。那么 \(res[i]\) 和 \(res[i-1]\) 都 \(--\)。
- 若 \(res[i]<0,res[i-1]<0\),那么,我们可以将 \(c_i--,c_{i-1}--\)。那么 \(res[i]\) 和 \(res[i-1]\) 都 \(++\)。
- 否则,我们可以将 \(c_{i-1}+=res[i-1]\)。同时 \(res[i-1]\) 清零。
- 需要注意,如果 \(c_i<0\) 或 \(c_{i+1}<0\),要强制变为 \(0\)。
- 最后得到的 \(b,c\) 即为最优解。证明可以考虑每一步的最优性,对答案的影响。
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
int T,n,a[110000],b[110000],c[110000];
ll ans;
inline int getres(int x){ return a[x]-(b[x]+c[x]);}
inline int myabs(int x){ return x>0?x:-x;}
int main(){
scanf("%d",&T);
while (T--){
scanf("%d",&n); n+=4;
for (int i=3;i<=n-2;i++) scanf("%d",&a[i]);
b[1]=0; c[1]=0; b[2]=0; c[2]=0;
int res1,res2,tmp;
for (int i=3;i<=n;i++){
b[i]=b[i-2]; c[i]=c[i-1];
res1=getres(i-1); res2=getres(i);
if (1ll*res1*res2>0){
if (res1>0) tmp=min(res1,res2);
else tmp=max(res1,res2);
c[i-1]+=tmp; c[i]+=tmp;
res1=getres(i-1); res2=getres(i);
}
b[i-1]+=res1; res1=getres(i-1);
if (b[i-1]<0){
tmp=-b[i-1];
b[i-1]+=tmp;
c[i-1]-=tmp; c[i]-=tmp;
res2=getres(i);
}
if (c[i-1]<0){
tmp=-c[i-1];
c[i-1]+=tmp; c[i]+=tmp;
b[i-1]-=tmp;
res2=getres(i);
}
}
b[1]=0; b[2]=0; c[1]=0; c[2]=0;
b[n-1]=0; b[n]=0; c[n-1]=0; c[n]=0;
ans=0;
for (int i=3;i<=n;i++) ans+=myabs(b[i]-b[i-2]);
for (int i=2;i<=n;i++) ans+=myabs(c[i]-c[i-1]);
printf("%lld\n",ans/2);
}
return 0;
}
Day2T1
- 现在只会 \(30pts\)。
- 先考虑树的做法,以 \(1\) 号点为根考虑。
- 首先每棵子树都是独立的,就是 \(Alice\) 往任何一棵子树走,和另外的子树没有关系。
- 设 \(g[i]\) 表示 \(i\) 的子树内,离 \(i\) 距离最近的在 \(S\) 中的点的距离,\(f[i]\) 表示初始时 \(Alice\) 在 \(i\),\(Bob\) 至少需要先额外行动多少个回合才能获胜(不可能获胜则为 \(INF\))。
- 那么 \(g[u]=\min_{v\in son[u]}g[v]+len(u,v)\),\(f[u]=\sum_{v\in son[u]}\max\{\min\{g[v]+1,f[v]\}-len(u,v)\}\)。(\(+1\) 是因为 \(Alice\) 先手,它要先到必须比 \(Alice\) 快一步)。
- 所有在 \(S\) 中的点,\(g\) 为 \(0\),\(f\) 也为 \(0\)(这个 \(f=0\) 可能不严谨,但是只要不对它父亲的 \(f\) 有贡献就可以了)。
- 所有在 \(T\) 中的点,\(f\) 为 \(INF\) (\(Bob\) 不可能赢)。
- 这样我们可以发现,能得 \(25pts\),分别是 \(1,2,10,11,12\)。其中 \(10,11,12\) 把一个 \(S\) 中的点拆开分成两个点,它就是一棵树了。在实现的时候不用显式拆开,因为这样的图对 \(dp\) 过程其实是没有影响的。
- 剩下的 \(5pts\),是 \(15\),\(|T|=1\)。我们可以发现,实际上就是 \(Bob\) 到 \(T\) 中不经过 \(1\) 号点的距离比 \(Alice\) 严格小就可以了,只需要输出 \(\max\{0,\min\{dist(s_i,t,\texttt{不经过1号点})\}-dist(1,t)+1\}\) 即可。
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
typedef pair<ll,int> pii;
const ll INF=1ll<<60;
int T,n,m,s,t;
ll f[510],g[510]; bool is_s[510],is_t[510];
int edgenum,vet[1100],val[1100],Next[1100],Head[510];
void addedge(int u,int v,int cost){
vet[++edgenum]=v; val[edgenum]=cost;
Next[edgenum]=Head[u]; Head[u]=edgenum;
}
void dfs(int u,int fa){
int v;
if (is_s[u]){ g[u]=0; return;}
g[u]=INF;
for (int e=Head[u];e;e=Next[e]){
v=vet[e];
if (v!=fa){
dfs(v,u);
g[u]=min(g[u],g[v]+val[e]);
}
}
}
void dfs2(int u,int fa){
int v;
if (is_s[u]){ f[u]=0; return;}
if (is_t[u]){ f[u]=INF; return;}
ll sum=0;
for (int e=Head[u];e;e=Next[e]){
v=vet[e];
if (v!=fa){
dfs2(v,u);
if (sum!=INF){
if (f[vet[e]]==INF&&g[vet[e]]==INF) sum=INF;
else sum+=max(min(g[vet[e]]+1,f[vet[e]])-val[e],0ll);
}
}
}
f[u]=sum;
}
ll dis[510]; bool vis[510];
priority_queue<pii,vector<pii>,greater<pii> > que;
void dijkstra(int s){
for (int i=1;i<=n;i++) dis[i]=INF,vis[i]=false;
que.push(pii(0,s));
dis[s]=0; vis[1]=true; int u;
while (!que.empty()){
u=que.top().second; que.pop();
if (vis[u]) continue;
vis[u]=true;
for (int e=Head[u];e;e=Next[e])
if (dis[vet[e]]>dis[u]+val[e]){
dis[vet[e]]=dis[u]+val[e];
que.push(pii(dis[vet[e]],vet[e]));
}
}
}
int main(){
scanf("%d",&T);
while (T--){
scanf("%d%d%d%d",&n,&m,&s,&t);
edgenum=0; for (int i=1;i<=n;i++) Head[i]=0;
int u,v,c;
for (int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&c); c++;
addedge(u,v,c); addedge(v,u,c);
}
for (int i=1;i<=n;i++) is_s[i]=false;
for (int i=1;i<=n;i++) is_t[i]=false;
for (int i=1;i<=s;i++) scanf("%d",&u),is_s[u]=true;
for (int i=1;i<=t;i++) scanf("%d",&u),is_t[u]=true;
if (t==1){
int x;
for (int i=1;i<=n;i++)
if (is_t[i]) x=i;
dijkstra(x);
ll ans=INF;
for (int i=1;i<=n;i++)
if (is_s[i]) ans=min(ans,dis[i]);
printf("%lld\n",min(1000000ll,max(ans-dis[1]+1,0ll)));
} else{
dfs(1,0); dfs2(1,0);
printf("%lld\n",min(1000000ll,f[1]));
}
}
return 0;
}
Day2T2
- 前置知识: \(min-max\) 容斥(期望形式):\(E(\min(S))=\sum_{T \subseteq S}(-1)^{|T|+1}E(\max(T))\),\(\min(S)=\sum_{T \subseteq S}(-1)^{|T|+1}\max(T)\)。本题中的 \(E(\min(S))\) 表示 \(S\) 中至少有一段长度为 \(k\) 的连续数字都出现的期望时间,\(E(\max(T))\) 表示 \(T\) 中每一段长度为 \(k\) 的连续数字都出现的期望时间( \(S,T\) 为段的集合)。
- \(70pts\)(细节多,而且做复杂了,其实 \(0/1\) 可以直接记在 \(dp\) 值里)
- 设 \(dp[i][j][0/1]\) 表示前 \(i\) 个数,选了 \(j\) 个数,集合大小奇偶性 \(0/1\) 的方案数(其中第 \(i\) 个数必须选),然后暴力 \(dp\) ,枚举上一个选了的数,时间复杂度 \(O(kn^2)\)。
- 经过思考,由于值域是 \(1\cdots 2m\) ,可以设 \(dp[i][j][0/1]\) 表示当前值为 \(i\) 的方案数,然后对于每个 \(i\) 都计算(不管有没有在 \(a\) 中出现过),在转移的时候再判断,没有出现的位置就不要转移过来。
- 初始时,\(dp[i][K][1]=1\)。
for (int j=1;j<=m;j++)
for (int k=0;k<=1;k++){
for (int t=1;t<i;t++)
if (max(j-K,t-(i-j))>=0)
dp[i][j][k]=(dp[i][j][k]+dp[t][max(j-K,t-(i-j))][k^1])%Mod;
/*
for (int t=1;t<i-1;t++)
if (max(j-1-K,t-(i-j))>=0)
dp[i-1][j-1][k]=(dp[i-1][j-1][k]+dp[t][max(j-1-K,t-(i-j))][k^1])%Mod;
*/
}
- 考虑 \(dp[i][j][k]\) 和 \(dp[i-1][j][k]\),相差的是一段前缀(上界不同)和 \(dp[i-1][j-1][k\oplus 1]\),然后一个前缀和即可,但是实际上细节也不少(前缀和要把在 \(a\) 中出现过的更新进去,并且还要考虑 \(j-1==K\ and \ k==0\)的情况),也调了不少时间,代码(\(70pts\)):
#include<cstdio>
#include<algorithm>
using namespace std;
const int Mod=998244353;
int m,K,a[210000],len[210000];
int dp[2][5100][2],sum[10005][5005][2];
int ans,Ans[210000];
bool vis[410000];
int getinv(int x){
if (x==1) return 1;
return 1ll*(Mod-Mod/x)*getinv(Mod%x)%Mod;
}
inline int getnum(int x,int y,int z){
if (x<0) return 0;
return (sum[x][y+1][z]-sum[x][y][z]+Mod)%Mod;
}
int main(){
scanf("%d%d",&m,&K);
for (int i=1;i<=m;i++) scanf("%d",&a[i]);
sort(a+1,a+m+1);
for (int i=m;i>=1;i--){
if (a[i+1]==a[i]+1) len[i]=len[i+1];
len[i]++;
}
for (int i=1;i<=m;i++)
if (len[i]>=K) vis[a[i]]=true;
for (int j=1;j<=m;j++) Ans[j]=(Ans[j-1]+1ll*m*getinv(j))%Mod;
if (vis[1]) sum[1][K][1]=1;
int now=0;
for (int i=2;i<=2*m;i++,now^=1){
for (int j=1;j<=m;j++)
for (int k=0;k<=1;k++)
dp[now][j][k]=(dp[now^1][j-1][k]+getnum(i-K-1,j-(K+1),k^1))%Mod;
dp[now^1][K][1]=(dp[now^1][K][1]+1)%Mod;
if (vis[i-1]){
for (int j=0;j<=m;j++)
for (int k=0;k<=1;k++)
if (k&1) ans=(ans+1ll*dp[now^1][j][k]*Ans[j])%Mod;
else ans=(ans-1ll*dp[now^1][j][k]*Ans[j]%Mod+Mod)%Mod;
}
for (int j=1;j<=m;j++)
for (int k=0;k<=1;k++){
if (vis[i-1]) dp[now][j][k]=(dp[now][j][k]+dp[now^1][j-1][k^1])%Mod;
if (vis[i]) sum[i][j][k]=(sum[i-1][j][k]+dp[now][j][k])%Mod;
else sum[i][j][k]=sum[i-1][j][k];
}
if (vis[i]) sum[i][K][1]=(sum[i][K][1]+1)%Mod;
}
if (vis[2*m]){
dp[now][K][1]=(dp[now][K][1]+1)%Mod;
for (int j=0;j<=m;j++)
for (int k=0;k<=1;k++)
if (k&1) ans=(ans+1ll*dp[now][j][k]*Ans[j])%Mod;
else ans=(ans-1ll*dp[now][j][k]*Ans[j]%Mod+Mod)%Mod;
}
printf("%d\n",ans);
return 0;
}
- \(100pts\)
- 我们可以发现一些性质:如果说我选了两个 \(len=k\) 的段,它们有交集(1.)或者紧挨着一起(2.),并且第二段的开头和第一段的开头不相邻,那么它们的贡献实际上是没有的。如下图:
- 为啥呢,由于 \(i..i+k-1\) 是连续的,\(i+t..i+t+k-1\) 也是连续的,那么 \(i..i+t+k-1\) 也是连续的(\(t\leq k\))。我们考虑这样:
- 如果我们固定最上面的和最下面的必选,那么相当于固定了 \(min-max\) 容斥中的 \(\max(S)\),那么中间的 \(i+1..i+t-1\) 开头的这些段选或不选对这个 \(\max\) 是没有影响的,影响的只是前面的容斥系数。
- \(i+1..i+t-1\) 开头的这些段选或不选的容斥系数之和,通过二项式定理,应该为 \(\binom{t-1}0(-1)^3+\binom{t-1}1(-1)^4+\binom{t-1}2(-1)^5+\cdots+\binom{t-1}{t-1}(-1)^{t+2}\),也就等于 \((-1)^3(1-1)^{t-1}=0\)。(每一项系数是 \((-1)^{|T|+1}\))
- 要注意 \(t=1\) 的时候是不能被抵消的。也就是只有可能是单独的 \(len=k\) 的或开头连续的 \(len=k+1\) 的(也就是两个 \(len=k\) 开头连续的拼起来)。且所有段不能相交,两个 \(len=k\) 的段之间不能连续。
- 现在考虑连续的 \(len=k+1\) 的段和 \(len=k\) 的段/ \(len=k\) 的段和 \(len=k+1\) 的段有没有贡献,这样的话相当于求长度为 \(2k+1\) 的会不会全被抵消:
- 显然,我们发现它的贡献并不能被消掉,因为 \(i+k\) 这个数它是必须被选的,也就是右端点在 \(i+k..i+2k-1\) 之间的段必须选择一个。
- 如果这些段都可以选或不选,通过二项式定理,那么贡献应该为 \(0\)。
- 但是必须选一个的话,那么 \(\binom k0(-1)^3\) 这一项消失了,那么贡献应该为 \(1\)。也就是我们可以强制钦定 \(k+1/k\) 或 \(k/k+1\) 中 \(k+1/k\) 有贡献。
- 再考虑两个连续的 \(len=k+1\) 的段有没有贡献。同样的:
-
情况1:右端点在 \(i+k+1..i+2k-1\) 之间的段必须选择一个。情况2:右端点在 \(i+k\) 和 \(i+2k\) 都选了。
-
两种情况满足其一才能算入答案。若均不满足,也就是 \(i+k+1..i+2k-1\) 都没选, \(i+k\) 和 \(i+2k\) 只选了一个或都没选。
-
如果这些段都可以选或不选,通过二项式定理,那么贡献应该为 \(0\)。全不满足的情况:
- \(i+k..i+2k\) 都没选,其它都选了,贡献 \((-1)^3=-1\)。
- \(i+k..i+2k-1\) 都没选,其它都选了,贡献 \((-1)^4=1\)。
- \(i+k+1..i+2k\) 都没选,其它都选了,贡献 \((-1)^4=1\)。
-
故总贡献即为 \(-1\),也就是 \((-1)^5\)。(而 \(k+1\) 本来就是长度为 \(k\) 的 \(4\) 个段,贡献即为 \(-1\))
-
所以现在可以转化为,只选 \(len=k/k+1\) 的段,其中 \(len_1=k,len_2=k/k+1\) 的两个段不能的开头不能相邻,所有的段不能相交,问方案数。
-
我们可以在原序列中选出一些不相交的长为 \(k+1\) 的段,然后考虑段中最后一个位置选/不选。可以发现,这样选出来不会出现 \(len_1=k,len_2=k/k+1\) 的开头相邻的两个段。记长度为 \(n\) 的连续段,这个算出来的生成函数为 \(g_n(x)\)。
-
但是有个问题,如果我选择了 \(n-k+1..n\) 这个长度为 \(k\) 的段,它不能通过 \(n-k+1..n+1\) 这个段然后不选 \(n+1\) 来得到,因为 \(n+1\) 越界了。但是我们会发现,如果选了这个段之后,就必定不能选 \(n-2k+1..n-k\) 这个长度为 \(k\) 的段了,这个的生成函数恰好为 \(-x^kg_{n-k}(x)\)。
-
记 \(f_n(x)\) 表示长度为 \(n\) 的连续段的答案生成函数。有
- 暴力计算时间复杂度为 \(\sum\limits_{i=0}^{\lfloor \frac n{k+1}\rfloor}i=O(\frac{n^2}{k^2})\)。然后它就水过了,水过了。。。。
- 当然,算 \(g_n\) 的这个可以优化,我们考虑通过分治来计算。先令 \(val_i=\binom{n-ik}{i}\),则有 \(ans(l,r)=\sum\limits_{i=l}^r val_i (x^{k+1}-x^k)^i=ans(l,mid)+ans(mid+1,r)\times(x^{k+1}-x^k)^{mid-l+1}\),其中 \((x^{k+1}-x^k)^y=(x-1)^yx^{ky}\),而计算 \((x-1)^y\) 的时间复杂度为 \(O(y)\),故这个分治的总时间复杂度为 \(T(l)=2T(\frac l2)+O(lk \log lk)\),而这里的 \(l\) 为原先的 \(\lfloor \frac n{k+1}\rfloor\),把 \(k\) 提出来可得时间复杂度为 \(O(k\times \frac nk\log^2 \frac nk+k\times\frac nk\log \frac nk\log k)\)。设 \(n,k\) 同阶,故总时间复杂度为 \(O(n \log^2 n)\)。
- 最后把这些生成函数乘起来,可以用分治 \(NTT\),我这里用了一个类似于合并果子的东西,每次取次数最小的两个多项式乘起来。故总时间复杂度为 \(O(m\log^2 m)\)。
#include<cstdio>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
typedef vector<int> vec;
typedef pair<int,int> pii;
const int Mod=998244353;
const int G=3; const int invG=(Mod+1)/3;
int m,k,a[210000],Ans[210000];
int fac[210000],inv[210000],invfac[210000];
vec poly[210000],v; int cnt;
priority_queue<pii,vector<pii>,greater<pii> > que;
inline int add(int x,int y){ return x+y>=Mod?x+y-Mod:x+y;}
inline int dec(int x,int y){ return x-y<0?x-y+Mod:x-y;}
inline int mul(int x,int y){ return 1ll*x*y%Mod;}
char Getchar(){
static char now[1<<20],*S,*T;
if (T==S){
T=(S=now)+fread(now,1,1<<20,stdin);
if (T==S) return EOF;
}
return *S++;
}
int read(){
int x=0,f=1;
char ch=Getchar();
while (ch<'0'||ch>'9'){
if (ch=='-') f=-1;
ch=Getchar();
}
while (ch<='9'&&ch>='0') x=x*10+ch-'0',ch=Getchar();
return x*f;
}
ll qpow(ll x,ll a){
ll res=1;
while (a){
if (a&1) res=res*x%Mod;
x=x*x%Mod; a>>=1;
}
return res;
}
inline ll getinv(int x){ return qpow(x,Mod-2);}
int rev[1100000];
int GPow[2][19][1100000];
void initG(){
for (int p=1;p<=18;p++){
int buf1=qpow(G,(Mod-1)/(1<<p));
int buf0=qpow(invG,(Mod-1)/(1<<p));
GPow[1][p][0]=GPow[0][p][0]=1;
for (int i=1;i<(1<<p);i++){
GPow[1][p][i]=mul(GPow[1][p][i-1],buf1);
GPow[0][p][i]=mul(GPow[0][p][i-1],buf0);
}
}
}
void NTT(vec &a,int len,int inv){
a.resize(len);
for (int i=0;i<len;i++)
if (i<rev[i]) swap(a[i],a[rev[i]]);
for (int l=2,cnt=1;l<=len;l<<=1,cnt++){
int m=l>>1;
for (int i=0;i<len;i+=l){
int *buf=GPow[inv][cnt];
for (int j=0;j<m;j++,buf++) {
int x=a[i+j],y=1ll*(*buf)*a[i+j+m]%Mod;
a[i+j]=add(x,y),a[i+j+m]=dec(x,y);
}
}
}
if (inv!=1){
ll inv=getinv(len);
for (int i=0;i<len;i++) a[i]=mul(a[i],inv);
}
}
void mult(vec &a,vec &b){
int n=(int)a.size()+(int)b.size()-1;
int bit=0; while ((1<<bit)<n) bit++;
int len=1<<bit;
for (int i=0;i<len;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
NTT(a,len,1); NTT(b,len,1);
a.resize(len);
for (int i=0;i<len;i++) a[i]=mul(a[i],b[i]);
NTT(a,len,0);
}
vec tmp3;
int val[210000];
inline int C(int x,int y){
if (x<y) return 0;
return mul(fac[x],mul(invfac[y],invfac[x-y]));
}
vec solve(int l,int r){
if (l==r){
vec tmp;
tmp.resize(1); tmp[0]=val[l];
return tmp;
}
int mid=(l+r)>>1;
vec tmp1=solve(l,mid),tmp2=solve(mid+1,r);
int len=mid-l+1,t=k*len;
tmp3.clear(); tmp3.resize(t+len+1);
for (int i=0;i<=len;i++)
if ((len-i)&1) tmp3[i+t]=dec(0,C(len,i));
else tmp3[i+t]=C(len,i);
mult(tmp3,tmp2);
tmp3.resize(max(tmp3.size(),tmp1.size()));
for (int i=0;i<(int)tmp1.size();i++) tmp3[i]=add(tmp3[i],tmp1[i]);
return tmp3;
}
vec v1,v2;
void getans(int n){
if (n<k) return;
int i,s;
for (i=0,s=0;s+i<=n;i++,s+=k) val[i+1]=C(n-s,i);
v1=solve(1,i);
for (i=0,s=k;s+i<=n;i++,s+=k) val[i+1]=C(n-s,i);
v2=solve(1,i);
v1.resize(max(v1.size(),v2.size()+k));
for (int i=0;i<(int)v2.size();i++) v1[i+k]=dec(v1[i+k],v2[i]);
while ((int)v1.size()>1&&!v1.back()) v1.pop_back();
poly[++cnt]=v1;
}
int main(){
m=read(); k=read(); initG();
fac[0]=1; for (int i=1;i<=m+1;i++) fac[i]=mul(fac[i-1],i);
inv[1]=1; for (int i=2;i<=m+1;i++) inv[i]=mul((Mod-Mod/i),inv[Mod%i]);
for (int i=1;i<=m;i++) Ans[i]=add(Ans[i-1],mul(m,inv[i]));
invfac[0]=1; for (int i=1;i<=m+1;i++) invfac[i]=mul(invfac[i-1],inv[i]);
for (int i=1;i<=m;i++) a[i]=read();
sort(a+1,a+m+1);
for (int i=1,j=0;i<=m;i=j+1){
j=i;
while (j<m&&a[j+1]==a[j]+1) j++;
getans(j-i+1);
}
for (int i=1;i<=cnt;i++) que.push(pii((int)poly[i].size(),i));
while (que.size()>1){
int x=que.top().second; que.pop();
int y=que.top().second; que.pop();
mult(poly[x],poly[y]);
que.push(pii((int)poly[x].size(),x));
}
int x=que.top().second,ans=0;
for (int i=1;i<(int)poly[x].size()&&i<=m;i++) ans=add(ans,mul(Ans[i],dec(0,poly[x][i])));
printf("%d\n",ans);
return 0;
}
Day2T3
- 现在只会 \(10pts\)。
- 就是直接枚举 \(b_1\),然后算出 \(x\) 代入后面的方程,看 \(b_i\) 符不符合条件,符合就输出结束。
#include<cstdio>
using namespace std;
typedef long long ll;
int T,m,err;
ll p,a[2100],c[2100];
inline ll add(ll x,ll y){return x+y>=p?x+y-p:x+y;}
inline ll dec(ll x,ll y){return x-y<0?x-y+p:x-y;}
ll mul(ll x,ll a){
ll res=0;
while (a){
if (a&1) res=add(res,x);
x=add(x,x); a>>=1;
}
return res;
}
ll qpow(ll x,ll a){
ll res=1;
while (a){
if (a&1) res=mul(res,x);
x=mul(x,x); a>>=1;
}
return res;
}
int main(){
scanf("%d",&T);
while (T--){
scanf("%d%lld%d",&m,&p,&err); err=(err+1)/2;
for (int i=1;i<=m;i++) scanf("%lld%lld",&a[i],&c[i]);
ll inva=qpow(a[1],p-2),x,y;
for (ll t=c[1]-err;t<=c[1]+err;t++){
x=t;
if (x<0) x+=p;
if (x>=p) x-=p;
x=mul(x,inva);
bool flag=true;
for (int i=2;i<=m;i++){
y=dec(mul(a[i],x),c[i]);
if (y>=p) y-=p;
if (y>err&&y<p-err){
flag=false;
break;
}
}
if (flag){
printf("%lld\n",x);
break;
}
}
}
return 0;
}