2025ICPC陕西省赛题解
一直想写的一场,拖拖拉拉终于写完了
L. easy
\(n\times n\) 矩阵,第 \(i\) 行第 \(j\) 列为 \((i-1)n+j\)。选数:每行恰选 \(2\) 个,每列恰选 \(2\) 个。求最小选数和。\(2\le n\le10^3\)。
发现将选的数拆分为行和列两部分的贡献,第 \(i\) 行对答案的总贡献为 \(2\times (i-1)n\),第 \(j\) 列对答案的贡献为 \(2j\),则总的答案即为
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
ll n;
cin>>n;
cout<<n*(n*n+1)<<"\n";
return 0;
}
C. gcd
给定 \(a,b\),求 \(x\in[1,10^{18}]\) 使 \(\gcd(a,x)=1\) 且 \(\gcd(b,x)>1\),若不存在输出 \(-1\)。\(a,b\le 10^{18}\)。
多测,\(T\leq 10^4\)
目标是找出 \(b\) 的一个因子 \(c\),使其不是 \(a\) 的一个因子,故 \(\gcd(a,c)=1\),对于任意一个数 \(x\),反复执行 \(x:=x/\gcd(x,a)\),即可使其不含 \(a\) 因子,因此我们设置 \(c=b\),进行这一过程即可保证 \(c\) 既是 \(b\) 的因子又可保证 \(\gcd(a,c)=1\)。
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
int T;cin>>T;
while(T--){
ll a,b;
cin>>a>>b;
while(__gcd(a,b)!=1){
b/=__gcd(a,b);
}
if(b==1)cout<<"-1\n";
else cout<<b<<"\n";
}
return 0;
}
G. student
\(n\) 个学生排成一行,分数 \(a_i\)。每次检查第 \(i\) 个学生,若当前队伍中左侧存在 \(\ge a_i\) 且右侧存在 \(\le a_i\),或左侧存在 \(\le a_i\) 且右侧存在 \(\ge a_i\),则该学生离开。求最终最少剩余人数。\(1\le n\le2\times10^5\),\(1\le a_i\le10^9\)。
显然操作到最后至少会剩下原序列的最大值和最小值两个数,不妨设在原序列的位置为 \(l,r(l<r)\),对于 \([2,l-1]\) 位置的一个数 \(a_i\),如果它大于等于 \(a_1\),则可以与在它右侧的最小值构成三元组消除,如果它小于等于 \(a_1\),则可以与最大值构成三元组消除。因此,\([2,l-1],[r+1,n-1]\) 这些位置的数一定可被消除。
我们只需要检查 \(1,n\) 这两个位置数是否是最大最小值即可,这是一份不难的分类讨论,具体见代码。
int n;
ll a[N],maxv=-INF,minv=INF;
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i];
maxv=max(maxv,a[i]);
minv=min(minv,a[i]);
}
if(n==1)return cout<<"1\n",0;//忘记特判这个了
if((a[1]==minv&&a[n]==maxv)||(a[1]==maxv&&a[n]==minv))
cout<<"2\n";
else if(a[1]==minv||a[1]==maxv||a[n]==minv||a[n]==maxv)
cout<<"3\n";
else cout<<"4\n";
return 0;
}
J. Win
给定字符串 \(s\),可至多插入 \(k\) 个任意字符,求最终串中连续子串
"lose"的最大出现次数。\(0\le k,|s|\le10^5\)。
由于 lose 四个字符均不相同,不用考虑字符串重叠计数的情况。
对于原串中已有的完整的 lose,破坏其完整性一定不会更优,不如直接计数。
之后为了更优,优先找加入 \(1\) 个字符就能构成完整 lose 的子段,如 los,loe,lse,ose。以此类推。不要忘了可以直接 \(4\) 个字符合成一个 lose。
int k,ans=0;
string s,t;
int main(){
cin>>k>>s;int n=s.length();
for(int i=0;i<n-3;++i){
string sub=s.substr(i,4);
if(sub=="lose"){
++ans;
s[i]=s[i+1]=s[i+2]=s[i+3]='#';
}
}
for(int i=0;i<n-2&&k>=1;++i){
string sub=s.substr(i,3);
if(sub=="ose"||sub=="lse"||sub=="loe"||sub=="los"){
++ans;--k;
s[i]=s[i+1]=s[i+2]='#';
}
}
for(int i=0;i<n-1&&k>=2;++i){
string sub=s.substr(i,2);
if(sub=="lo"||sub=="ls"||sub=="le"||sub=="os"||sub=="oe"||sub=="se"){
++ans;k-=2;
s[i]=s[i+1]='#';
}
}
for(int i=0;i<n&&k>=3;++i){
if(s[i]=='l'||s[i]=='o'||s[i]=='s'||s[i]=='e'){
++ans;
k-=3;
}
}
ans+=k/4;//又忘了
cout<<ans<<"\n";
return 0;
}
A. Color
长度为 \(n\) 的带子,第 \(i\) 段初始颜色 \(c_i\)。操作 \((l,r,c)\) 将区间 \([l,r]\) 染成颜色 \(c\),代价为 \(w_c+(r-l+1)\),其中 \(w_c\) 是颜色 \(c\) 的单价。对每个颜色 \(c=1,\dots,n\),求将整个带子染成颜色 \(c\) 的最小总代价。\(1\le n\le2\times10^5\),\(1\le c_i,w_i\le n\)。
比较套路的一道题,假设要染上颜色 \(c\),如果按对每一段极长的颜色不为 \(c\) 的段染色,则总的花费为 段数乘上 \(w_c\) 加上这些段的总长度。考虑对于相邻的两段,它们被一个颜色全 \(c\) 的段相隔,如果这三段一起作为一个区间染色,花费相较与原来的变化为 \(-w_c\) 加上中间颜色全 \(c\) 段的段长。比较中间颜色全 \(c\) 段的段长和 \(w_c\) 的大小即可做出判断。
int n,c[N];
ll w[N];
vector<ttfa>lis[N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%d",&c[i]);
for(int i=1;i<=n;++i)scanf("%lld",&w[i]);
for(int i=1;i<=n;++i){
if(lis[c[i]].size()&&lis[c[i]].back().second==i-1){
lis[c[i]].back().second=i;
}else{
lis[c[i]].push_back({i,i});
}
}
//ll ans=INF;
for(int i=1;i<=n;++i){
ll tot=n,div=0;
for(auto [l,r]:lis[i]){
tot-=(r-l+1);
if(l==1||r==n)continue;
++div;
if(r-l+1<w[i]){
tot+=(r-l+1);
tot-=w[i];
}
}
tot+=w[i]*(div+1);
printf("%lld ",tot);
}
puts("");
return 0;
}
K. Welfare
有 \(n\) 头无私牛和 \(m\) 头自私牛。选项 A:选择 A 的 \(k\) 头牛平分 \(x\) kg,每头得 \(x/k\);选项 B:每头牛单独得 \(y\) kg。无私牛集体决策使总草量最大,且知道自私牛的行为;自私牛独立同时决策,每个自私牛假定其他自私牛都选 B,若选 A 严格优于选 B 则选 A,否则选 B。求总分配草量。\(T\le10^5\),\(n,m,x,y\le10^9\)。
这种题还是首先注意读题,这些自私牛的行为已经明确了,且每只自私牛的决策是相同的。然后我们可以得到关系
- \(\frac{x}{k+1}>y\) 时,自私牛选择参与平分,答案为 \(x+(n-k)y\)
- \(\frac{x}{k+1}\leq y\) 时,自私牛选择单独获得 \(y\),答案为 \(x+(n-k+m)y\)
两个答案均在 \(k\) 尽量小时取得尽量大,对于第一种情况,\(k\) 取 \(0\);对于第二种情况 \(k\) 取 \(\lceil \frac{x}{y}-1 \rceil\)。分别计算即可。
同时需要注意 \(n,m,y=0\) 时的边界情况,特判均较为容易。
ll n,m,x,y;
ll check(ll k){
if(k<0||k>n)return 0;
if(x>(k+1)*y)return x+(n-k)*y;
return (k==0?0:x)+(n-k+m)*y;
}
int main(){
int T;scanf("%d",&T);
while(T--){
ll ans=0;
scanf("%lld%lld%lld%lld",&n,&m,&x,&y);
if(m==0){
ans=max(x+(n-1)*y,n*y);
if(n==0)ans=0;
printf("%lld\n",ans);
continue;
}
ans=max(ans,check(0));
ans=max(ans,check(1));
if(y!=0)ans=max(ans,check((x+y-1)/y-1));
printf("%lld\n",ans);
}
return 0;
}
E. Printer
字符集大小 \(m\)(\(1\le m\le26\)),字符串 \(s\) 长度 \(n\le20\)。初始手指在
'a',光标在空串开头。每次可:移动手指(代价 \(\text{cost}_{x,y}\),满足三角不等式)、左右移光标一位(代价 \(t\))或打印手指所指字符到光标右侧(代价 \(t\))。求打印 \(s\) 的最小总代价。\(0\le t,\text{cost}\le10^5\)。
状态压缩动态规划,定义 \(dp_{S,p}\) 为当前已经输入的字符集合为 \(S\),且最后一个输入的字符下标为 \(p\) 的最小花费,转移较为显然
其中 \(dist(S\setminus\{p\},q,p)\) 为 \(S\setminus\{p\}\) 串中光标从 \(q\) 末端移动到 \(p\) 位置的移动次数,根据 \(S,p\) 易于求得。
状态数 \(n\times 2^n\),单个状态转移 \(n\) 次,总时间复杂度 \(O(n^22^n)\)。
如果你是在 CF 上交题,TLE了可能需要改变数组定义次序,具体见代码。
int n,m;
int cost[N][N],t;
char s[N];
int dp[1<<21][21];//反正按这个顺序写不会T
int dfsp(int S,int p){
//printf("dfsp %d %d %d\n",p,S,dp[p][S]);
if(dp[S][p]!=-1)return dp[S][p];
int tmp=0x3f3f3f3f,T=S^(1<<p);
if(T>=S)assert(0);
for(int i=p-1,cnt=0;i>=0;--i){
if((S>>i)&1){
++cnt;
tmp=min(tmp,dfsp(T,i)+cost[s[i]-'a'][s[p]-'a']+cnt*t);
}
}
for(int i=p+1,cnt=1;i<n;++i){
if((S>>i)&1){
++cnt;
tmp=min(tmp,dfsp(T,i)+cost[s[i]-'a'][s[p]-'a']+cnt*t);
}
}
return dp[S][p]=tmp;
}
int main(){
memset(dp,-1,sizeof(dp));
scanf("%d%d%d",&n,&m,&t);scanf("%s",s);
for(int i=0;i<m;++i){
for(int j=0;j<m;++j){
scanf("%d",&cost[i][j]);
}
}
dp[0][0]=0;
for(int i=0;i<n;++i){
dp[1<<i][i]=cost[0][s[i]-'a']+t;
}
int ans=0x3f3f3f3f;
for(int i=0;i<n;++i){
ans=min(ans,dfsp((1<<n)-1,i));
}
printf("%d\n",ans);
return 0;
}
D. Stock
初始值为 \(v\),有 \(n\) 个操作,每个操作为
* x_i或+ x_i$。可任意排列操作顺序,令 \(v_i\) 为第 \(i\) 次操作后的值,最大化 \(\frac{1}{n}\sum_{i=1}^n v_i\)。\(1\le n\le30\),\(1\le v\le10\),乘法因子 \(x_i\in(1,1.2)\),加法增量 \(x_i\in(0,1000)\),均为实数,精度 \(10^{-9}\)。
一个比较显然的观察是对于加法操作,先加大的后加小的;对于乘法操作,先乘大的再乘小的(详细的正确性证明可以看官方题解)。因此如果我们固定了 \(n\) 次操作中哪些位置进行加法,哪些位置进行乘法,具体的分配方式和答案是已知。
则我们考虑搜索进行加法(或乘法,是等价的)的位置,设加法操作一共 \(m\) 个,需要枚举的方案数为 \(\binom{n}{m}\),当 \(m=\frac{n}{2}\) 时有最大值,计算 \(n=30\) 时,\(\binom{30}{15}=155,117,520\),可以接受。注意实现时不要写成 \(O(n\binom{n}{\frac{n}{2}})\) 的复杂度。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>ttfa;
const int N=32;
const ll INF=0x3f3f3f3f3f3f3f3f;
int n,cm,ca;
double v,mul[N],add[N],ans=0;
char opt[2];
void dfs(int i,int j,double val,double sum){
if(i==ca&&j==cm){
ans=max(ans,sum/n);
return;
}
if(i<ca)dfs(i+1,j,val+add[i+1],sum+val+add[i+1]);
if(j<cm)dfs(i,j+1,val*mul[j+1],sum+val*mul[j+1]);
}
int main(){
scanf("%d%lf",&n,&v);
for(int i=1;i<=n;++i){
double x;
scanf("%s%lf",opt,&x);
if(opt[0]=='+')add[++ca]=x;
if(opt[0]=='*')mul[++cm]=x;
}
sort(add+1,add+1+ca,greater<double>());
sort(mul+1,mul+1+cm,greater<double>());
dfs(0,0,v,0);
printf("%.10lf\n",ans);
return 0;
}
M. Nightmare
链上 \(n\) 个点,颜色 \(c_i\in\{0,1\}\),权重 \(w_i\)。每时刻所有极大同色连通块同时行动:若相邻存在一个异色连通块比它重(比较权重和,相等时比块内最小编号,更小者更重),则该连通块整体翻转颜色。\(q\) 次询问 \((t_i,x_i)\),求第 \(t_i\) 时刻位置 \(x_i\) 的颜色。\(1\le n,q\le2\times10^5\),\(t_i\le10^9\),\(w_i\le10^9\)。
为了这碟醋包的这盘饺子属于是了,其实回过头来看这道题也不算很难。
首先模仿数学中“极值点”的定义,定义一个区间的“极重块”为相邻连通块均比它轻的一个连通块。整个链上可能会有很多个“极重块”,在一次翻转中它们的颜色不会改变而其余连通块的颜色会改变。一次翻转过后由于“极重块”两侧的连通块颜色会改变而与它相同,“极重块”的重量(或者说覆盖的区间)会改变,可能与其余异色“极重块”相邻,也可能与其余同色“极重块”合并,但无论如何“极重块”的个数不增,且一次翻转后总的连通块个数会减少“极重块”同量级的数量。因而我们只需正确维护当前时刻的“极重块”信息即可。
考虑使用并查集维护所有的连通块,并查集需记录每一个连通块权重和,以及左右端点以便定位相邻连通块。使用 set 或类似数据结构维护“极重块”,每一时刻,与“极重块”相邻的块,与极重块合并(并查集维护);不与“极重块”相邻的块,其颜色随时间的奇偶性可知,无需特别维护。极重块吸收相邻块完成后,由于可能“极重块”之间合并,如 10101,需要去重;由于"极重块"可能与其余"极重块"相邻而无法作为下一时刻的“极重块”,需要删除,同时由于在当前时刻及之前这一连通块的颜色不会改变,需要相应改变此时刻的颜色以便后续查询。
具体实现可见代码,算是比较简洁的赛时一发通过代码了(虽然是赛后根据记忆重写的)。
typedef long long ll;
typedef pair<int,int>ttfa;
const int N=300005;
const ll llINF=0x3f3f3f3f3f3f3f3f;
const int INF=0x3f3f3f3f;
struct query{
int t,loc,id;
}que[N];
char s[N];
int n,m,ans[N];
ll a[N];
int col[N],fa[N],lef[N],rit[N],tag[N];
inline int fidf(int x){
if(x<1||x>n)return 0;
return x==fa[x]?x:fa[x]=fidf(fa[x]);
}
inline void merge(int x,int y){
x=fidf(x),y=fidf(y);
if(x==y||!x||!y)return;
fa[x]=y;a[y]+=a[x];
lef[y]=min(lef[y],lef[x]);
rit[y]=max(rit[y],rit[x]);
}
inline int getans(int loc,int t){
int u=fidf(loc);
if(tag[u])return col[u];
return col[u]^(t&1);
}
int main(){
scanf("%d%s",&n,s+1);
for(int i=1;i<=n;++i){
fa[i]=i,lef[i]=rit[i]=i;
scanf("%lld",&a[i]);
col[i]=s[i]-'0';
if(col[i]==col[i-1])merge(i,i-1);
}
scanf("%d",&m);
for(int i=1;i<=m;++i){
scanf("%d%d",&que[i].t,&que[i].loc);
que[i].id=i;
}
sort(que+1,que+1+m,[](query &x,query &y){return x.t<y.t;});
set<int>b,bb;
for(int i=1;i<=n;++i){
int u=fidf(i);
int l=fidf(lef[u]-1),r=fidf(rit[u]+1);
if((!l||a[u]>a[l])&&(!r||a[u]>=a[r]))
b.insert(u),tag[u]=1;
}
int loc=1;
for(int t=1;t<=n;++t){
bb.clear();
for(auto u:b){
int l=fidf(lef[u]-1),r=fidf(rit[u]+1);
merge(l,u);merge(r,u);
}
for(auto u:b){
if(fidf(u)!=u)continue;
int l=fidf(lef[u]-1),r=fidf(rit[u]+1);
if((l&&a[u]<=a[l])||(r&&a[u]<a[r])){
tag[u]=0;
col[u]^=(t&1);
}else bb.insert(u);
}
b=bb;
for(;que[loc].t<=t&&loc<=m;++loc){
ans[que[loc].id]=getans(que[loc].loc,t);
}
}
for(;loc<=m;++loc)ans[que[loc].id]=getans(que[loc].loc,que[loc].t);
for(int i=1;i<=m;++i){
if(ans[i])putchar('1');
else putchar('0');
}puts("");
return 0;
}
F. LOCK S
有根树,根为 \(1\),点权 \(v_i\)。Dawn 先手,每次可占领任意未占点。Tsuki 初始在场外,第一次行动必须占领根 \(1\);之后每次必须从当前占领点移动到其一个未被占领的儿子,且该儿子的 \(v\) 值在所有未被占领的儿子中最大(相同则取编号最小)。若轮到某人时无合法移动,则立即结束。求在双方最优下,Dawn 占领点权和减去Tsuki 占领点权和的最大值。\(n\le2\times10^5\),\(v_i\le10^9\)。
后手的选点一定构成一条从根往下的路径,我们可以在枚举路径的过程中求出先手必须选择哪些点才能形成这条路径,先手还能额外选择一些点,只需要选取剩余的点中最大的一些点即可。可以使用权值线段树维护点集,求出剩余点集中最大的若干个点点权之和。
具体实现见代码,复杂度 \(O(n\log n)\)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll>ttfa;
const int N=500005;
const ll INF=0x3f3f3f3f3f3f3f3f;
int n;
ll a[N];
ttfa lis[N];int pos[N];
vector<int>tar[N];
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)
ll sum[N<<2];int num[N<<2];
inline void pushup(int p){
sum[p]=sum[ls]+sum[rs];
num[p]=num[ls]+num[rs];
}
void build(int p,int l,int r){
if(l==r){
sum[p]=lis[l].first;
num[p]=1;
return;
}
build(ls,l,mid);
build(rs,mid+1,r);
pushup(p);
}
void update(int p,int l,int r,int L,int f){
if(l==r){
sum[p]+=f*lis[l].first;
num[p]+=f;
return;
}
if(L<=mid)update(ls,l,mid,L,f);
else update(rs,mid+1,r,L,f);
pushup(p);
}
ll getsum(int p,int l,int r,int res){
if(num[p]==res)return sum[p];
if(l==r){
if(res>=num[p])return sum[p];
return 0;
}
if(num[ls]<res)return sum[ls]+getsum(rs,mid+1,r,res-num[ls]);
return getsum(ls,l,mid,res);
}
#undef ls
#undef rs
#undef mid
ll ans,pre[N];int dep[N];
void check(ll sa,ll sb,int res){
if(res<0)return;
if(res)sa+=getsum(1,1,n,min(res,num[1]));
ans=max(ans,sa-sb);
}
void dfs(int u,int f,int cnt,ll sum){
dep[u]=dep[f]+1;
pre[u]=pre[f]+a[u];
vector<ttfa>nxt;
for(auto v:tar[u]){
if(v==f)continue;
nxt.push_back({a[v],v});
}
sort(nxt.begin(),nxt.end(),[](ttfa &x,ttfa &y){
return x.first==y.first?x.second<y.second:x.first>y.first;
});
for(auto [w,v]:nxt){
update(1,1,n,pos[v],-1);
//printf("del %d\n",pos[v]);
if(dep[u]+1>=cnt)dfs(v,u,cnt,sum);
sum+=w;++cnt;
}
check(sum,pre[u],dep[u]+1-cnt);
//printf("%d %lld %lld %d %d\n",u,sum,pre[u],dep[u]+1,cnt);
for(auto [w,v]:nxt){
update(1,1,n,pos[v],+1);
//printf("add %d\n",pos[v]);
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%lld",&a[i]);
lis[i]={a[i],i};
}
for(int i=1;i<n;++i){
int u,v;scanf("%d%d",&u,&v);
tar[u].push_back(v);
tar[v].push_back(u);
}
sort(lis+1,lis+1+n);
for(int i=1;i<=n;++i){
ttfa tmp={a[i],i};
pos[i]=lower_bound(lis+1,lis+1+n,tmp)-lis;
pos[i]=n-pos[i]+1;
}
reverse(lis+1,lis+1+n);
build(1,1,n);
ans=a[1];
update(1,1,n,pos[1],-1);dfs(1,0,0,0);
printf("%lld\n",ans);
return 0;
}
我也不知道为什么赛时就想着是一个困难的DP。
I. magic
给定序列 \(b_1,\dots,b_n\),参数 \(k\)(\(2\le n\le5\times10^5\),\(1\le k\le\lfloor n/2\rfloor\))。一次操作:选 \(x\in[k,n-k]\),令 \(t=b_{x+1}-b_x\),对 \(d=1..k\) 执行 \(b_{x+d}\leftarrow b_{x+d}-t\),\(b_{x-d+1}\leftarrow b_{x-d+1}+t\)。可进行任意次操作,求最终可得到的不同序列数量,对 \(10^9+7\) 取模。
没办法补题的时候想了很久也没想出来,也没法总结出什么好的思路,只能按官方题解说了。
令一个长为 \(n+1\) 的序列 \(a\),当 \(i>n\) 时 \(a_i=0\);当 \(i\leq n\) 时,\(a_i=a_{i+k}+b_i\)。
令一个长为 \(n+1\) 的序列 \(d\),\(d_{i}=a_{i}-a_{i-1}\)。
则执行一次操作时,相当于将 \(d\) 序列 \(x\) 和 \(x+k\) 位置元素交换。我画了个表便于理解此过程,具体细节只能自己体会了。
| 下标 | \(1\) | \(2\) | \(3\) | \(k=4\) | \(5\) | \(6\) | \(7\) | \(2k=8\) | \(n+1\) |
|---|---|---|---|---|---|---|---|---|---|
| \(b\) 原先 | \(b_1\) | \(b_2\) | \(b_3\) | \(b_4\) | \(b_5\) | \(b_6\) | \(b_7\) | \(b_8\) | \(0\) |
| \(b\) 操作 | \(b_1+b_5-b_4\) | \(b_2+b_5-b_4\) | \(b_3+b_5-b_4\) | \(b_5\) | \(b_4\) | \(b_6+b_4-b_5\) | \(b_7+b_4-b_5\) | \(b_8+b_4-b_5\) | \(0\) |
| \(a\) 原先 | \(b_1+b_5\) | \(b_2+b_6\) | \(b_3+b_7\) | \(b_4+b_8\) | \(b_5\) | \(b_6\) | \(b_7\) | \(b_8\) | \(0\) |
| \(a\) 操作 | \(b_1+b_5\) | \(b_2+b_6\) | \(b_3+b_7\) | \(b_4+b_8\) | \(b_4\) | \(b_6+b_4-b_5\) | \(b_7+b_4-b_5\) | \(b_8+b_4-b_5\) | \(0\) |
| \(d\) 原先 | \(b_5-b_4-b_8\) | \(-b_8\) | |||||||
| \(d\) 操作 | \(-b_8\) | \(b_5-b_4-b_8\) |
然后我们对于 \(d\) 序列求解一个新的问题即可
操作可以交换一个长为 \(n+1\) 的序列 \(x\) 和 \(x+k\) 位置的元素(\(x\in [k+1,n-k+1]\)),任意次操作后不同的序列个数。
可以发现 \(\bmod k\) 不同的下标无法相互交换,则对于 \(\bmod k\) 相同的下标考虑方案数最后相乘即可。\(\bmod k\) 相同的下标中,设每个值 \(d_i\) 的个数共有 \(c_i\) 个,一共有 \(m\) 个下标,\(t\) 个不同的值,则这些数组成的答案为
const int N=500005;
const ll INF=0x3f3f3f3f3f3f3f3f;
namespace binom{
const ll MOD=1000000007;
const int RANGE=2000000;
inline ll fpr(ll b,ll t=MOD-2,ll x=1ll){
for(;t;t>>=1,b=b*b%MOD)
if(t&1)x=x*b%MOD;
return x;
}
ll bin_mul[RANGE+5],bin_inv[RANGE+5];
inline void binom_init(){
bin_mul[0]=1;
for(int i=1;i<=RANGE;++i)bin_mul[i]=bin_mul[i-1]*i%MOD;
bin_inv[RANGE]=fpr(bin_mul[RANGE]);
for(int i=RANGE-1;i>=0;--i)bin_inv[i]=bin_inv[i+1]*(i+1)%MOD;
}
inline ll C(ll n,ll m){
if(m>n){puts("binom range wrong");exit(0);}
return bin_mul[n]*bin_inv[m]%MOD*bin_inv[n-m]%MOD;
}
}
using namespace binom;
const ll P=1000000007;
int n,k;
ll b[N],a[N],d[N];
int main(){
binom_init();
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i){
scanf("%lld",&b[i]);
}
for(int i=n;i>=1;--i)a[i]=a[min(i+k,n+1)]+b[i];
for(int i=1;i<=n+1;++i){
d[i]=a[i]-a[i-1];
}
ll ans=1ll;
for(int i=k+1;i<=2*k;++i){
map<ll,int>bot;ll tot=0;
for(int j=0;i+j*k<=n+1;++j){
++bot[d[i+j*k]];
++tot;
}
for(auto [x,cnt]:bot){
ans=ans*C(tot,cnt)%P;
tot-=cnt;
}
}
printf("%lld\n",ans);
return 0;
}
还有两题后面有时间再补吧,感谢观看

浙公网安备 33010602011771号