【比赛记录】2025 暑假集训模拟赛合集I
2025CSP-S模拟赛29
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 50 | 50 | 30 | 0 | 130 | 10/17 |
A. 二分图匹配
首先考虑一个朴素的 DP:设 \(f_{i,j}\) 表示 \(S_1[1\dots i]\) 和 \(S_2[1\dots j]\) 的最大匹配数量。发现空间开不下。注意到 \(f\) 数组的值最大为 \(\min(n,m)\),考虑交换下标与值域,设 \(f_{i,j}\) 表示 \(S_1[1\dots i]\) 匹配了 \(j\) 位在 \(S_2\) 上的最短前缀。记 \(g_{i,j}\) 表示 \(S_2\) 中第 \(i\) 位之后第一次出现字符 \(j\) 的位置,于是有转移:\(f_{i,j}=\min(f_{i-1,j},g_{f_{i-1,j-1},{S_1}_i})\)。时间复杂度 \(O(26m+n^2)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e3+5,maxm=1e6+5,inf=1e9;
int n,m,f[maxn][maxn],g[maxm][30],pos[30];
string s1,s2;
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m>>s1>>s2;
s1=" "+s1,s2="A"+s2;
memset(pos,0x3f,sizeof(pos));
for(int i=m;~i;i--){
for(int j=0;j<=25;j++){
g[i][j]=pos[j];
}
pos[s2[i]-'A']=i;
}
memset(f,0x3f,sizeof(f));
for(int i=0;i<=n;i++){
f[i][0]=0;
for(int j=1;j<=i;j++){
f[i][j]=min(f[i-1][j],f[i-1][j-1]>=inf?inf:g[f[i-1][j-1]][s1[i]-'A']);
}
}
for(int i=n;~i;i--){
if(f[n][i]<=m){
cout<<i;
break;
}
}
return 0;
}
}
int main(){return asbt::main();}
B. 虚图
考虑直接对这些关键点二进制分组,当前位为 \(1\) 的连一条 \(S\xrightarrow{0} a_i\),否则连一条 \(a_i\xrightarrow{0} T\),从 \(S\) 跑最短路,用 \(dis_T\) 更新答案即可。因为两个不同的点至少有一位不同,所以这样的分组方式遍历了所有组合。时间复杂度 \(O((n+m)\log^2n)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=2e5+5;
const ll inf=1e18;
int n,m,kk,a[maxn];
ll dis[maxn];
bool vis[maxn];
vector<pii> e[maxn];
struct edge{
int u,v,w;
}E[maxn];
priority_queue<pii> q;
il void dijkstra(int u){
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
dis[u]=0,q.push(mp(0,u));
while(q.size()){
int x=q.top().sec;
q.pop();
if(vis[x]){
continue;
}
vis[x]=1;
for(pii i:e[x]){
int v=i.fir,w=i.sec;
if(!vis[v]&&dis[v]>dis[x]+w){
dis[v]=dis[x]+w;
q.push(mp(-dis[v],v));
}
}
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m>>kk;
for(int i=1;i<=m;i++){
cin>>E[i].u>>E[i].v>>E[i].w;
}
for(int i=1;i<=kk;i++){
cin>>a[i];
}
int S=n+1,T=n=S+1;
ll ans=inf;
for(int i=0;i<=17;i++){
for(int j=1;j<=m;j++){
e[E[j].u].pb(mp(E[j].v,E[j].w));
e[E[j].v].pb(mp(E[j].u,E[j].w));
}
for(int j=1;j<=kk;j++){
if(j>>i&1){
e[S].pb(mp(a[j],0));
}
else{
e[a[j]].pb(mp(T,0));
}
}
dijkstra(S),ans=min(ans,dis[T]);
for(int j=1;j<=n;j++){
e[j].clear();
}
}
cout<<ans;
return 0;
}
}
int main(){return asbt::main();}
C. 冒泡
考虑将答案差分,分为 \([1,r]\) 和 \([1,l]\) 两部分。
对于 \([1,x]\),考虑枚举当前考虑的数有前端有多少与 \(x\) 相同。假设 \(x\) 为 \(\overline{a_1a_2a_3\dots}\),如果当前枚举的为 \(\overline{a_1k}\;(k<a_2)\),那么后面的位数都可以随意乱选。将此时的答案拆成已确定的部分、未确定的部分和二者之间。设未确定的还有 \(len\) 位,对于已确定的部分,求出其逆序对数 \(tot\),这一部分答案为 \(tot\times10^{len}\);对于未确定的部分,任选两个位置组成逆序对,其他位置乱选,答案为 \(\frac{len\times(len-1)}{2}\times45\times10^{len-2}\);二者之间的部分,假设前面有一个 \(c\),若希望构成逆序对则后面一个位置的取值有 \(0\dots c-1\) 共 \(c\) 种,贡献为 \(c\times len\times10^{len-1}\)。
注意这样无法算入 \(x\) 的贡献,需要另外求出来。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=5e5+5,mod=1e9+7;
int T,pw[maxn];
string l,r;
il int calc(string s){
int x=s.size(),tot=0,sum=0,res=0,cnt[15]={0};
for(char ch:s){
int c=ch^48;
x--;
for(int i=0;i<c;i++){
if(x>=2){
(res+=x*(x-1)/2%mod*45%mod*pw[x-2])%=mod;
}
if(x>=1){
(res+=(sum+i)*x%mod*pw[x-1])%=mod;
}
(res+=(tot+cnt[i+1])*pw[x])%=mod;
}
(tot+=cnt[c+1])%=mod,(sum+=c)%=mod;
for(int i=0;i<=c;i++){
cnt[i]++;
}
}
return res;
}
il int nxd(string s){
int cnt[15]={0},res=0;
for(char ch:s){
int c=ch^48;
for(int i=c+1;i<=9;i++){
(res+=cnt[i])%=mod;
}
cnt[c]++;
}
return res;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
pw[0]=1;
for(int i=1;i<=5e5;i++){
pw[i]=pw[i-1]*10%mod;
}
cin>>T;
while(T--){
cin>>l>>r;
cout<<(calc(r)-calc(l)+nxd(r)+mod)%mod<<"\n";
}
return 0;
}
}
signed main(){return asbt::main();}
D. 亲戚
记 \(a_i\) 表示包含了 \(x\) 的限制组成的集合,大小为 \(1\) 的限制不算。考虑组成了树之后,对于叶子节点 \(u\),一定有 \(a_u\subseteq a_{fa_u}\)。于是想到一个构造树的方式:每次选择一对点使得 \(a_u\subseteq a_v\) 且 \(u\) 还未被加入至树中,将 \(u\) 加入至树中。此时将所有包含 \(u\) 的限制的大小减一,如果减成 \(1\) 了那么就删去这个限制。这样的复杂度为 \(O(n^3m)\)。显然 \(a\) 可以用 bitset 维护,优化至 \(O(\frac{n^3m}{w})\)。考虑将 \(u\) 加入树中,此时大小减成 \(1\) 的限制中剩下的那个点一定是 \(v\),于是可以用栈维护每个点被包含于哪些点,删去 \(u\) 时将 \(v\) 暴力重构。时间复杂度 \(O(\frac{n^2m}{w})\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e3+5;
int T,n,m,cnt[maxn];
int top[maxn],stk[maxn][maxn];
bool vis[maxn];
bitset<maxn> a[maxn];
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>T;
while(T--){
cin>>n>>m;
for(int i=1;i<=n;i++){
a[i].reset(),vis[i]=0;
}
for(int i=1,x;i<=m;i++){
cin>>cnt[i];
if(cnt[i]==1){
cin>>x;
continue;
}
for(int j=1;j<=cnt[i];j++){
cin>>x;
a[x].set(i);
}
}
for(int i=1;i<=n;i++){
top[i]=0;
for(int j=1;j<=n;j++){
if(i==j){
continue;
}
if((a[i]|a[j])==a[j]){
stk[i][++top[i]]=j;
}
}
}
for(int i=1;i<n;i++){
for(int j=1;j<=n;j++){
if(!vis[j]&&top[j]){
int k=stk[j][top[j]--];
while(top[j]&&vis[k]){
k=stk[j][top[j]--];
}
if(vis[k]){
continue;
}
for(int x=1;x<=m;x++){
if(a[j][x]&&--cnt[x]==1){
a[k].reset(x);
}
}
top[k]=0;
for(int x=1;x<=n;x++){
if(x==k){
continue;
}
if((a[k]|a[x])==a[x]){
stk[k][++top[k]]=x;
}
}
vis[j]=1;
goto togo1;
}
}
cout<<"NO\n";
goto togo2;
togo1:;
}
cout<<"YES\n";
togo2:;
}
return 0;
}
}
int main(){return asbt::main();}
/*
8 8
4 1 5 4 6
1 4
3 2 1 6
3 2 7 3
4 2 5 7 1
7 5 7 8 2 6 1 3
7 3 8 5 6 4 7 1
5 3 8 1 6 4
*/
2025CSP-S模拟赛30
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 10 | 50 | - | - | 60 | 7/15 |
A. 天才俱乐部
注意最后一步得出的只是必要条件,因此枚举 \(\sum_{i=1}^{n}a_i-s\) 的因子再一一验证即可。时间复杂度 \(O(tn\sqrt{\sum_{i=1}^{n}a_i-s})\)。注意特判 \(\sum_{i=1}^{n}a_i=s\) 的情况。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
int T,n,m,a[105];
il bool chk(int x){
int t=0;
for(int i=1;i<=n;i++){
t+=a[i]%x;
}
return t==m;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>T;
while(T--){
cin>>n>>m;
int sum=0;
for(int i=1;i<=n;i++){
cin>>a[i];
sum+=a[i];
}
if(sum==m){
cout<<"YES\n";
continue;
}
sum-=m;
for(int i=1;i<=sum/i;i++){
if(sum%i==0&&(chk(i)||chk(sum/i))){
cout<<"YES\n";
goto togo;
}
}
cout<<"NO\n";
togo:;
}
return 0;
}
}
int main(){return asbt::main();}
B. 实战教学
考虑二分答案 \(mid\),首先如果 \(\exist i,a_i+b_i>mid\) 那么一定不合法。于是对于每个配对 \((x,y)\),一定有 \(b_y\le mid-a_x\)。发现随着 \(a_x\) 的增大满足条件的 \(y\) 的数量不增,于是考虑从大到小枚举 \(a_x\),贪心地在所有满足条件的 \(y\) 中选择 \(a_y\) 最大的那一个。可以用线段树维护,时间复杂度线性对数方。
似乎可以用栈做到线性对数,但是 ds 代替思考还是太棒了😜
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int n,a[maxn],b[maxn],p[maxn],q[maxn],fp[maxn];
bool vis[maxn];
struct seg{
int mxp,mxa,mxb;
seg(int mxp=0,int mxa=0,int mxb=0):mxp(mxp),mxa(mxa),mxb(mxb){}
il seg operator+(const seg &x)const{
seg res;
if(mxa>x.mxa){
res=*this;
}
else{
res=x;
}
res.mxb=max(mxb,x.mxb);
return res;
}
}tr[maxn<<2];
il void pushup(int id){
tr[id]=tr[lid]+tr[rid];
}
il void build(int id,int l,int r){
if(l==r){
tr[id]=seg(p[l],a[p[l]],b[p[l]]);
return ;
}
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
pushup(id);
}
il seg query(int id,int l,int r,int lim){
if(l==r){
return tr[id].mxb<=lim?tr[id]:seg();
}
int mid=(l+r)>>1;
if(tr[lid].mxb<=lim){
return tr[lid]+query(rid,mid+1,r,lim);
}
return query(lid,l,mid,lim);
}
il void upd(int id,int l,int r,int x){
if(l==r){
tr[id].mxa=0;
return ;
}
int mid=(l+r)>>1;
if(x<=mid){
upd(lid,l,mid,x);
}
else{
upd(rid,mid+1,r,x);
}
pushup(id);
}
il bool chk(int x){
for(int i=1;i<=n;i++){
if(a[i]+b[i]>x){
return 0;
}
vis[i]=0;
}
// cout<<x<<"\n";
build(1,1,n);
for(int i=1;i<=n;i++){
if(vis[q[i]]){
continue;
}
vis[q[i]]=1;
upd(1,1,n,fp[q[i]]);
seg t=query(1,1,n,x-a[q[i]]);
if(!t.mxa){
return 0;
}
// cout<<q[i]<<" "<<t.mxp<<"\n";
vis[t.mxp]=1;
upd(1,1,n,fp[t.mxp]);
}
return 1;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
n<<=1;
for(int i=1;i<=n;i++){
cin>>a[i];
p[i]=q[i]=i;
}
for(int i=1;i<=n;i++){
cin>>b[i];
}
sort(p+1,p+n+1,[](int x,int y){return b[x]<b[y];});
// for(int i=1;i<=n;i++){
// cout<<p[i]<<" ";
// }
// cout<<"\n";
for(int i=1;i<=n;i++){
fp[p[i]]=i;
}
sort(q+1,q+n+1,[](int x,int y){return a[x]>a[y];});
ll l=1,r=2e9;
while(l<r){
int mid=(l+r)>>1;
if(chk(mid)){
r=mid;
}
else{
l=mid+1;
}
}
cout<<l;
return 0;
}
}
int main(){return asbt::main();}
C. 穿越银匙之门
无根树非常难办,考虑先枚举根,将它变成有根树。那么根就不能动了。当然也有可能根也需要先操作一下,那就暴力枚举根的操作。
然后此时,我们发现一个点需要操作的充要条件为它在两棵树上的父亲不同。考虑判断是否有解,发现在 \(A\) 树中,儿子一定在父亲前面修改;在 \(B\) 树中,儿子一定在父亲后面修改。那么可以就此跑一个拓扑排序,来判断可行性。同时这也引出了一个判不合法的方式:在 \(A\) 树中,如果儿子不需要修改而父亲需要修改,那么无解。时间复杂度 \(O(tn^3)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int inf=1e9;
int T,n,af[55],bf[55],ad[55],q[55],gd[55];
bool f[55];
vector<int> A[55],B[55],G[55];
il void dfs(int u,int fa,vector<int>* e,int *p){
p[u]=fa;
for(int v:e[u]){
if(v==fa){
continue;
}
dfs(v,u,e,p);
}
}
il int calc(){
int cnt=0;
for(int i=1;i<=n;i++){
f[i]=af[i]!=bf[i];
cnt+=f[i];
G[i].clear(),gd[i]=0;
}
for(int i=1;i<=n;i++){
if(!f[i]&&f[af[i]]){
return inf;
}
if(f[i]){
if(f[af[i]]){
G[i].pb(af[i]),gd[af[i]]++;
}
if(f[bf[i]]){
G[bf[i]].pb(i),gd[i]++;
}
}
}
int hd=1,tl=0;
for(int i=1;i<=n;i++){
if(f[i]&&!gd[i]){
q[++tl]=i;
}
}
while(hd<=tl){
int u=q[hd++];
for(int v:G[u]){
if(--gd[v]==0){
q[++tl]=v;
}
}
}
return tl==cnt?cnt:inf;
}
il void solve(){
cin>>n;
for(int i=1;i<=n;i++){
ad[i]=0;
A[i].clear(),B[i].clear();
}
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
A[u].pb(v),A[v].pb(u);
ad[u]++,ad[v]++;
}
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
B[u].pb(v),B[v].pb(u);
}
dfs(1,0,A,af),dfs(1,0,B,bf);
for(int i=1;i<=n;i++){
if(af[i]!=bf[i]){
goto togo;
}
}
cout<<0<<"\n";
return ;
togo:;
int ans=inf;
for(int i=1;i<=n;i++){
if(ad[i]>1){
continue;
}
dfs(i,0,B,bf);
for(int j=1;j<=n;j++){
if(i==j){
continue;
}
dfs(j,0,A,af);
af[j]=i,af[i]=0;
ans=min(ans,calc()+1);
}
}
cout<<(ans==inf?-1:ans)<<"\n";
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>T;
while(T--){
solve();
}
return 0;
}
}
int main(){return asbt::main();}
D. 绳网委托
考虑将为 \(0\) 的位置赋值为 \(1\),\(1\) 的位置赋值为 \(-1\),记前缀和为 \(pre\),\(1\) 的个数为 \(cnt\),LIS 中最后一个 \(0\) 在原序列中的位置为 \(x\),那么长度为 \(cnt+pre_x\)。而本题还有翻转操作,可以注意到翻转之后的前缀和就是原序列的一段前缀再加上 \(k\) 段不交的子串。
经过这个转化后,和这道题就非常类似了。考虑一个反悔贪心:每次选出最大的子串并加入答案,同时将这个子串中的数取相反数。这样如果后面选到的子串和这次的子串有重合,就相当于是将这次的子串反悔掉了一部分。线段树维护即可。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define lid id<<1
#define rid id<<1|1
#define fir first
#define sec second
using namespace std;
namespace asbt{
const int maxn=2e5+5;
int n,a[maxn],b[maxn],ans[maxn];
bool tag[maxn<<2];
struct node{
int l,r,sum;
node(int l=0,int r=0,int sum=0):l(l),r(r),sum(sum){}
il node operator+(const node &x)const{
return sum>x.sum?*this:x;
}
};
struct seg{
int sum,lm,rm,mm,pl,pr,ml,mr;
seg(int sum=0,int lm=0,int rm=0,int mm=0,int pl=0,int pr=0,int ml=0,int mr=0)
:sum(sum),lm(lm),rm(rm),mm(mm),pl(pl),pr(pr),ml(ml),mr(mr){}
il seg operator+(const seg &x)const{
seg res;
res.sum=sum+x.sum;
node t1=node(0,pl,lm)+node(0,x.pl,sum+x.lm);
node t2=node(x.pr,0,x.rm)+node(pr,0,rm+x.sum);
node t3=node(ml,mr,mm)+node(x.ml,x.mr,x.mm)+node(pr,x.pl,rm+x.lm);
res.lm=t1.sum,res.pl=t1.r;
res.rm=t2.sum,res.pr=t2.l;
res.mm=t3.sum,res.ml=t3.l,res.mr=t3.r;
return res;
}
};
pair<seg,seg> tr[maxn<<2];
il void pushup(int id){
tr[id].fir=tr[lid].fir+tr[rid].fir;
tr[id].sec=tr[lid].sec+tr[rid].sec;
}
il void pushtag(int id){
tag[id]^=1;
swap(tr[id].fir,tr[id].sec);
}
il void pushdown(int id){
if(tag[id]){
pushtag(lid),pushtag(rid);
tag[id]=0;
}
}
il void build(int id,int l,int r){
if(l==r){
tr[id].fir=seg(b[l],b[l],b[l],b[l],l,l,l,l);
tr[id].sec=seg(-b[l],-b[l],-b[l],-b[l],l,l,l,l);
return ;
}
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
pushup(id);
}
il void upd(int id,int L,int R,int l,int r){
if(L>=l&&R<=r){
pushtag(id);
return ;
}
pushdown(id);
int mid=(L+R)>>1;
if(l<=mid){
upd(lid,L,mid,l,r);
}
if(r>mid){
upd(rid,mid+1,R,l,r);
}
pushup(id);
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
int cnt=0;
for(int i=1;i<=n;i++){
cin>>a[i]>>b[i];
if(a[i]){
cnt+=b[i],b[i]=-b[i];
}
}
build(1,1,n);
if(tr[1].fir.lm>0){
cnt+=tr[1].fir.lm;
upd(1,1,n,1,tr[1].fir.pl);
}
for(int i=1;i<=n;i++){
ans[i]=ans[i-1]+tr[1].fir.mm;
upd(1,1,n,tr[1].fir.ml,tr[1].fir.mr);
}
for(int i=1;i<=n;i++){
ans[i]=max(ans[i-1],ans[i]);
}
for(int i=0;i<=n;i++){
cout<<cnt+ans[i]<<"\n";
}
return 0;
}
}
signed main(){return asbt::main();}
2025CSP-S模拟赛31
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 50 | 100 | 30 | 49 | 229 | 5/18 |
A. 花海
一个暴力的做法是枚举 \(x_1\),\(x_2\),\(y_1\),\(y_2\),此时的权值即为 \(sum_{x_2,y_2}-sum_{x_1-1,y_2}-sum_{x_2,y_1-1}+sum_{x_1-1,y_1-1}-sum_{x_2-1,y_2-1}+sum_{x_1,y_2-1}+sum_{x_2-1,y_1}-sum_{x_1,y_1}\)。考虑令 \(n\le m\),于是 \(n\le\sqrt{2\times10^5}\)。枚举 \(x_1\),\(x_2\),\(y_1\),动态维护 \(y_2\) 的最大贡献。具体地,倒过来枚举 \(y_1\),用当前的 \(y_1\) 更新答案后再更新 \(sum_{x_2,y_2}-sum_{x_1-1,y_2}-sum_{x_2-1,y_2-1}+sum_{x_1,y_2-1}\) 的最大值。时间复杂度 \(O(n^2m)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=455,inf=1e9;
int n,m;
vector<int> a[maxn];
int main(){
freopen("flower.in","r",stdin);
freopen("flower.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
if(n<m){
for(int i=0;i<=n;i++){
a[i].resize(m+5);
}
// for(int i=0;i<=n;i++){
// for(int j=0;j<=m;j++){
// cout<<a[i][j]<<" ";
// }
// cout<<"\n";
// }
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
}
}
else{
swap(n,m);
for(int i=0;i<=n;i++){
a[i].resize(m+5);
}
for(int j=1;j<=m;j++){
for(int i=1;i<=n;i++){
cin>>a[i][j];
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
// cout<<a[i][j]<<" ";
a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
}
// cout<<"\n";
}
int ans=-inf;
for(int x1=1;x1<=n;x1++){
for(int x2=x1+1;x2<=n;x2++){
int tmp=-inf;
for(int y1=m;y1;y1--){
ans=max(ans,tmp-a[x2][y1-1]+a[x1-1][y1-1]+a[x2-1][y1]-a[x1][y1]);
tmp=max(tmp,a[x2][y1]-a[x1-1][y1]-a[x2-1][y1-1]+a[x1][y1-1]);
}
}
}
cout<<ans;
return 0;
}
}
int main(){return asbt::main();}
B. 划分
考虑 DP,设 \(dp_i\) 表示考虑了前 \(i\) 个位置的最小规模和,于是有转移:
满足条件的 \(j\) 显然是一段连续区间,可以二分出来。用单调栈维护区间 \(\max\) 的变化,线段树优化即可。时间复杂度线性对数。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define lwrb lower_bound
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
const int maxn=1e5+5,inf=1e18;
int n,m,a[maxn],sa[maxn],stk[maxn],top;
int mn[maxn<<2],tag[maxn<<2],dp[maxn];
il void pushup(int id){
mn[id]=min(mn[lid],mn[rid]);
}
il void pushtag(int id,int v){
tag[id]+=v,mn[id]+=v;
}
il void pushdown(int id){
if(tag[id]){
pushtag(lid,tag[id]);
pushtag(rid,tag[id]);
tag[id]=0;
}
}
il void add(int id,int L,int R,int l,int r,int v){
if(l>r){
return ;
}
if(L>=l&&R<=r){
pushtag(id,v);
return ;
}
pushdown(id);
int mid=(L+R)>>1;
if(l<=mid){
add(lid,L,mid,l,r,v);
}
if(r>mid){
add(rid,mid+1,R,l,r,v);
}
pushup(id);
}
il int query(int id,int L,int R,int l,int r){
if(L>=l&&R<=r){
return mn[id];
}
pushdown(id);
int mid=(L+R)>>1,res=inf;
if(l<=mid){
res=min(res,query(lid,L,mid,l,r));
}
if(r>mid){
res=min(res,query(rid,mid+1,R,l,r));
}
return res;
}
struct{
int st[maxn][18],Log[maxn];
il void build(){
for(int i=2;i<=n;i++){
Log[i]=Log[i>>1]+1;
}
for(int i=1;i<=n;i++){
st[i][0]=a[i];
}
for(int j=1;j<=Log[n];j++){
for(int i=1;i+(1<<j)-1<=n;i++){
st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
}
}
il int query(int l,int r){
int p=Log[r-l+1];
return max(st[l][p],st[r-(1<<p)+1][p]);
}
}ST;
int main(){
freopen("split.in","r",stdin);
freopen("split.out","w",stdout);
// freopen("split3.in","r",stdin);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
sa[i]=sa[i-1]+a[i];
}
if(n<=1e3){
ST.build();
memset(dp,0x3f,sizeof(dp));
dp[0]=0;
for(int i=1;i<=n;i++){
for(int j=i-1;~j;j--){
if(sa[i]-sa[j]>m){
break;
}
dp[i]=min(dp[i],dp[j]+ST.query(j+1,i));
}
}
}
else{
stk[++top]=0;
for(int i=1;i<=n;i++){
add(1,0,n,i-1,i-1,dp[i-1]+a[i]);
while(top&&a[stk[top]]<a[i]){
add(1,0,n,stk[top-1],stk[top]-1,a[i]-a[stk[top]]);
top--;
}
stk[++top]=i;
dp[i]=query(1,0,n,lwrb(sa,sa+i,sa[i]-m)-sa,i-1);
}
}
cout<<dp[n];
return 0;
}
}
signed main(){return asbt::main();}
C. 落子无悔
对于两个序列 \(a\) 和 \(b\),我们设 \(s0\) 和 \(s1\) 分别为 \(0\) 和 \(1\) 的数量,由邻项交换可知 \(a\) 排在 \(b\) 前面更优等价于 \(\frac{s0_a}{s1_a}\ge\frac{s0_b}{s1_b}\)。于是我们的做法就比较显然了:每次选出 \(\frac{s0}{s1}\) 最大的点,将其与父亲节点合并,用 set 维护即可。注意把根特判掉。时间复杂度线性对数。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e5+5;
int n,fa[maxn],p[maxn],a[maxn];
int _0[maxn],_1[maxn],nxt[maxn],lst[maxn];
il int find(int x){
return x!=p[x]?p[x]=find(p[x]):x;
}
struct node{
int u,_0,_1;
node(int u=0,int _0=0,int _1=0):u(u),_0(_0),_1(_1){}
il bool operator<(const node &x)const{
if(_0*1ll*x._1==x._0*1ll*_1){
return u<x.u;
}
return _0*1ll*x._1>x._0*1ll*_1;
}
};
set<node> S;
int main(){
freopen("board.in","r",stdin);
freopen("board.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
for(int i=2;i<=n;i++){
cin>>fa[i];
}
for(int i=1;i<=n;i++){
cin>>a[i];
_0[i]=a[i]^1,_1[i]=a[i];
p[i]=i,nxt[i]=lst[i]=i;
}
for(int i=2;i<=n;i++){
S.insert(node(i,_0[i],_1[i]));
}
// for(node x:S){
// cout<<x.u<<" "<<x._0<<" "<<x._1<<"\n";
// }
for(int i=1;i<n;i++){
// cout<<i<<"\n";
int u=S.begin()->u,v=find(fa[u]);
S.erase(node(u,_0[u],_1[u]));
if(v!=1){
S.erase(node(v,_0[v],_1[v]));
}
_0[v]+=_0[u],_1[v]+=_1[u];
nxt[lst[v]]=u,lst[v]=lst[u];
p[u]=v;
if(v!=1){
S.insert(node(v,_0[v],_1[v]));
}
}
ll cnt=0,ans=0;
for(int i=1;;i=nxt[i]){
// cout<<i<<"\n";
if(!a[i]){
ans+=cnt;
}
else{
cnt++;
}
if(i==nxt[i]){
break;
}
}
cout<<ans;
return 0;
}
}
int main(){return asbt::main();}
D. 体测
2025CSP-S模拟赛33
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 100 | 100 | 30 | - | 230 | 3/20 |
A. 机器人
dfs,暴力枚举每一步有没有遇到障碍物,用 set 维护答案即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
using namespace std;
namespace asbt{
int n;
bool vis[50][50],ban[50][50];
string s;
set<pii> S;
il void dfs(int x,int y,int d){
if(d>n){
S.insert(mp(x,y));
return ;
}
int nx,ny;
switch(s[d]){
case 'L':{
nx=x-1,ny=y;
break;
}
case 'R':{
nx=x+1,ny=y;
break;
}
case 'D':{
nx=x,ny=y-1;
break;
}
default:{
nx=x,ny=y+1;
break;
}
}
if(!vis[nx+20][ny+20]){
vis[nx+20][ny+20]=1;
dfs(nx,ny,d+1);
ban[nx+20][ny+20]=1;
dfs(x,y,d+1);
ban[nx+20][ny+20]=0;
vis[nx+20][ny+20]=0;
}
else if(ban[nx+20][ny+20]){
dfs(x,y,d+1);
}
else{
dfs(nx,ny,d+1);
}
}
int main(){
// system("fc robot3.ans my.out");
// freopen("robot3.in","r",stdin);
// freopen("my.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>s;
s=" "+s;
vis[20][20]=1;
dfs(0,0,1);
cout<<S.size()<<"\n";
for(pii x:S){
cout<<x.fir<<" "<<x.sec<<"\n";
}
return 0;
}
}
int main(){return asbt::main();}
B. 旅行
首先考虑一个暴力的思路:将每个点拆成若干个点,代表可能的颜色,对边设点,将边与端点对应的颜色用并查集合并即可。发现每次合并在一段时间后就会撤销,使用可撤销并查集,线段树分治即可。时间复杂度线性对数方,需要卡常。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
namespace IO{
const int bufsz=1<<20;
char ibuf[bufsz],*p1=ibuf,*p2=ibuf;
#define getchar() (p1==p2&&(p2=(p1=ibuf)+fread(ibuf,1,bufsz,stdin),p1==p2)?EOF:*p1++)
il int read(){
char ch=getchar();
while(ch<'0'||ch>'9'){
ch=getchar();
}
int x=0u;
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x;
}
#undef getchar
char obuf[bufsz],*p3=obuf,s[50];
#define flush() (fwrite(obuf,1,p3-obuf,stdout),p3=obuf)
#define putchar(ch) (p3==obuf+bufsz&&flush(),*p3++=(ch))
il void write(int x){
int top=0u;
do{
s[++top]=x%10|48;
x/=10;
}while(x);
while(top){
putchar(s[top--]);
}
putchar('\n');
}
#undef putchar
struct FL{
~FL(){
flush();
}
}fl;
#undef flush
}
using IO::read;
using IO::write;
const int maxn=1e5+5;
int T,n,m,cnt,tot,top,col[maxn],pos[maxn],ban[maxn];
int fa[maxn*5],sz[maxn*5],tz[maxn*5],ans[maxn];
pii stk[maxn<<1];
map<pii,int> bid;
map<int,int> did[maxn];
vector<pii> g[maxn<<2];
struct{
int u,v,w;
}e[maxn];
il int find(int x){
return x!=fa[x]?find(fa[x]):x;
}
il void merge(int u,int v){
u=find(u),v=find(v);
if(u==v){
return ;
}
if(sz[u]>sz[v]){
swap(u,v);
}
stk[++top]=mp(u,v);
sz[v]+=sz[u],fa[u]=v;
if(tz[u]&&tz[v]){
--tot;
}
tz[v]+=tz[u];
}
il void che(){
int u=stk[top].fir,v=stk[top].sec;
--top,fa[u]=u,sz[v]-=sz[u],tz[v]-=tz[u];
if(tz[u]&&tz[v]){
++tot;
}
}
int l,r;
pii x;
il void add(int id,int L,int R){
if(L>=l&&R<=r){
g[id].pb(x);
return ;
}
int mid=(L+R)>>1;
if(l<=mid){
add(lid,L,mid);
}
if(r>mid){
add(rid,mid+1,R);
}
}
il void solve(int id,int l,int r){
int ttp=top;
for(pii i:g[id]){
merge(i.fir,i.sec);
}
if(l==r){
ans[l]=tot;
}
else{
int mid=(l+r)>>1;
solve(lid,l,mid);
solve(rid,mid+1,r);
}
while(top>ttp){
che();
}
g[id].clear();
}
int main(){
// system("fc tour3.ans my.out");
// freopen("tour3.in","r",stdin);
// freopen("my.out","w",stdout);
T=read();
while(T--){
cnt=n=read(),m=read();
bid.clear();
for(int i=1u;i<=n;++i){
did[i].clear();
ban[i]=0u;
}
for(int i=1u,u,v,w;i<=n;++i){
u=read(),v=read(),w=read();
// cout<<u<<" "<<v<<" "<<w<<"\n";
if(u>v){
swap(u,v);
}
did[u][w]=++cnt;
did[v][w]=++cnt;
if(bid.count(mp(u,v))){
ban[bid[mp(u,v)]]=i;
}
else{
bid[mp(u,v)]=i;
}
col[i]=w,pos[i]=0u;
}
for(int i=1u,u,v,w;i<=m;++i){
u=read(),v=read(),w=read();
if(u>v){
swap(u,v);
}
did[u][w]=++cnt;
did[v][w]=++cnt;
e[i]={u,v,w};
}
for(int i=1u;i<=m;++i){
int u=e[i].u,v=e[i].v,w=e[i].w;
int k=bid[mp(u,v)];
if(col[k]==w){
continue;
}
l=pos[k],r=i-1;
x=mp(k,did[u][col[k]]),add(1u,0u,m);
x=mp(k,did[v][col[k]]),add(1u,0u,m);
col[k]=w,pos[k]=i;
if(ban[k]){
k=ban[k];
l=pos[k],r=i-1;
x=mp(k,did[u][col[k]]),add(1u,0u,m);
x=mp(k,did[v][col[k]]),add(1u,0u,m);
col[k]=w,pos[k]=i;
}
}
for(pair<pii,int> i:bid){
int u=i.fir.fir,v=i.fir.sec,k=i.sec;
l=pos[k],r=m;
x=mp(k,did[u][col[k]]),add(1u,0u,m);
x=mp(k,did[v][col[k]]),add(1u,0u,m);
if(ban[k]){
k=ban[k];
l=pos[k],r=m;
x=mp(k,did[u][col[k]]),add(1u,0u,m);
x=mp(k,did[v][col[k]]),add(1u,0u,m);
}
}
for(int i=1u;i<=cnt;++i){
fa[i]=i,sz[i]=1u,tz[i]=i<=n;
}
tot=n,solve(1u,0u,m);
// cerr<<ans[0]<<"\n";
for(int i=1u;i<=m;++i){
write(ans[i]);
}
}
// cerr<<mx<<"\n";
return 0;
}
}
int main(){return asbt::main();}
C. 点餐
考虑第 \(i\) 个客人怎么做,将 \(b\) 升序排序,枚举 \(j\ge i\),强制选择 \(j\),那么 \(b\) 的最大值即为 \(b_j\),再在前 \(j-1\) 个位置中选择前 \(i-1\) 小的 \(a\) 即可,需要主席树。进一步可以发现的是,最优决策点是有单调性的。于是分治即可。时间复杂度线性对数方。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define lwrb lower_bound
using namespace std;
namespace asbt{
const int maxn=2e5+5,maxm=maxn*20,inf=1e18;
int n,tot,lsh[maxn],tong[maxn],rt[maxn],ans[maxn];
int cnt,ls[maxm],rs[maxm],sz[maxm],sum[maxm];
struct node{
int a,b;
}p[maxn];
il void pushup(int id){
sz[id]=sz[ls[id]]+sz[rs[id]];
sum[id]=sum[ls[id]]+sum[rs[id]];
}
il void insert(int &p,int q,int l,int r,int x){
p=++cnt;
if(l==r){
sum[p]=lsh[x],sz[p]=1;
return ;
}
int mid=(l+r)>>1;
if(x<=mid){
rs[p]=rs[q];
insert(ls[p],ls[q],l,mid,x);
}
else{
ls[p]=ls[q];
insert(rs[p],rs[q],mid+1,r,x);
}
pushup(p);
}
il int query(int id,int l,int r,int x){
if(!id||!x){
return 0;
}
if(sz[id]==x){
return sum[id];
}
int mid=(l+r)>>1;
if(x<=sz[ls[id]]){
return query(ls[id],l,mid,x);
}
return sum[ls[id]]+query(rs[id],mid+1,r,x-sz[ls[id]]);
}
il int calc(int x,int y){
// cout<<x<<" "<<y<<" "<<query(rt[y-1],1,n,x-1)<<"\n";
return query(rt[y-1],1,n,x-1)+lsh[p[y].a]+p[y].b;
}
il void solve(int L,int R,int l,int r){
if(L>R){
return ;
}
int mid=(L+R)>>1;
int &res=ans[mid]=inf,p=-1;
for(int i=max(l,mid);i<=r;i++){
int t=calc(mid,i);
if(t<res){
res=t,p=i;
}
}
// cout<<mid<<" "<<p<<" "<<calc(mid,p)<<"\n";
solve(L,mid-1,l,p);
solve(mid+1,R,p,r);
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>p[i].a>>p[i].b;
lsh[++tot]=p[i].a;
}
sort(lsh+1,lsh+tot+1);
for(int i=1;i<=n;i++){
int t=lwrb(lsh+1,lsh+tot+1,p[i].a)-lsh;
p[i].a=t+tong[t],tong[t]++;
}
sort(p+1,p+n+1,[](node x,node y){return x.b<y.b||x.b==y.b&&x.a<y.a;});
for(int i=1;i<=n;i++){
insert(rt[i],rt[i-1],1,n,p[i].a);
}
solve(1,n,1,n);
for(int i=1;i<=n;i++){
cout<<ans[i]<<"\n";
}
return 0;
}
}
signed main(){return asbt::main();}
D. 无穷括号序列
设 \(f_{k,i}\) 表示 \(B^k\) 中第 \(i\) 个左括号的下标,于是答案可以通过差分+二分求出,问题变为求 \(f\)。
考虑转移,分两种情况:
-
\(f_{k-1,i}\) 下一个位置为右括号,
()会变成)(,于是 \(f_{k,i}=f_{k-1,i}+1\) -
\(f_{k+1,i}\) 下一个位置为左括号,
((会变成(*,于是 \(f_{k,i}=f_{k-1,i+1}-1\)。
于是 \(f_{k,i}=\min(f_{k-1,i}+1,f_{k-1,i+1}-1)\)。
我们假设 \(f_{k,i}\) 是从 \(f_{0,j}\) 转移过来的,于是转移过程中 \(\min\) 中的第二项选了 \(j-i\) 次,第一项选了 \(k-j+i\) 次。于是有:
考虑用 ST 表维护 \(F_j=f_{0,j}-2j\) 的最小值,但是序列太长了。记序列 \(A\) 中有 \(m\) 个左括号,于是 \(f_{0,j+m}=f_{0,j}+n\)。于是:
于是只需要预处理出 \(F_0\) 到 \(F_{2m-1}\) 即可。但是如果 \(k>m\),我们依然求不了,考虑 \(n\) 和 \(2m\) 的关系。
-
\(n\ge2m\),则 \(F_{j+m}\ge F_j\),最小值在 \([i,i+m)\) 中。
-
\(n<2m\),类似地,最小值在 \((i+k-m,i+k]\) 中。
这样就又可以求了。轻微卡常,减少取模即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e5+5,inf=1e9;
int T,n,m,q,f[maxn],Log[maxn],st[maxn][19];
string s;
il int query(int l,int r){
int p=Log[r-l+1];
return min(st[l][p],st[r-(1<<p)+1][p]);
}
il int F(int k,int i){
#define qum(x) (x=x>=0?x:x+m)
if(k<=m){
int p=i%m;qum(p);
int t=(i-p)/m;
return query(p,p+k)+k+2*i+(n-2*m)*t;
}
if(n>=m<<1){
int p=i%m;qum(p);
int t=(i-p)/m;
return query(p,p+m-1)+k+2*i+(n-2*m)*t;
}
int p=(i+k)%m;qum(p);
int t=(i+k-m-p)/m;
return query(p+1,p+m)+k+2*i+(n-2*m)*t;
#undef qum
}
il int g(int k,int p){
int l=-inf,r=inf;
while(l<r){
int mid=(l+r+1)>>1;
if(F(k,mid)<=p){
l=mid;
}
else{
r=mid-1;
}
}
return l;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
for(int i=2;i<=2e5;i++){
Log[i]=Log[i>>1]+1;
}
cin>>T;
while(T--){
cin>>s>>q;
n=s.size(),m=0;
for(int i=0;i<n;i++){
if(s[i]=='('){
f[m++]=i;
}
}
if(!m){
while(q--){
int k,l,r;
cin>>k>>l>>r;
cout<<0<<'\n';
}
continue;
}
for(int i=0;i<m;i++){
f[i+m]=f[i]+n;
}
for(int i=0;i<m<<1;i++){
st[i][0]=f[i]-i*2;
}
for(int j=1;j<=17;j++){
for(int i=0;i+(1<<j)-1<m<<1;i++){
st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
}
while(q--){
int k,l,r;
cin>>k>>l>>r;
cout<<g(k,r)-g(k,l-1)<<'\n';
}
}
return 0;
}
}
int main(){return asbt::main();}
2025CSP-S模拟赛34(牛宏昊)
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 50 | 60 | 0 | 35 | 145 | 3/17 |
A. 酒吧
假设我们最终选的是 \(b_1,b_2,\dots,b_k\),那么答案为 \(\sum_{i=1}^{k-1}(b_{i+1}-b_i)\times(p_{b_i}+p_{b_{i+1}})\)。发现这实际上是梯形面积的两倍。那么我们相当于是要在所有的点 \((i,p_i)\) 中选一些点使线下面积最大,用单调栈维护上凸包即可。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=5e5+5;
int n,a[maxn],q[maxn],tl;
int main(){
freopen("bar.in","r",stdin);
freopen("bar.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
#define K(i,j) (a[j]-a[i])*1.0l/(j-i)
for(int i=1;i<=n;i++){
while(tl>1&&K(q[tl-1],q[tl])<K(q[tl],i)){
tl--;
}
q[++tl]=i;
}
#undef K
int ans=0;
for(int i=1;i<tl;i++){
ans+=(q[i+1]-q[i])*(a[q[i]]+a[q[i+1]]);
}
cout<<ans;
return 0;
}
}
signed main(){return asbt::main();}
B. 逆转
考虑二分答案 \(mid\),求出 \(f_i\) 表示从 \(1\) 到 \(i\) 和不超过 \(mid\) 最多能选多少个,\(g_i\) 类似地表示后缀。考虑在 \(1\) 到 \(i\) 中尽可能多选,显然从小到大选最优。于是用大根堆维护,若能加入则直接加,否则若比堆顶小则替换堆顶即可。时间复杂度线性对数方。CF上卡常
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
namespace IO{
const int bufsz=1<<20;
char ibuf[bufsz],*p1=ibuf,*p2=ibuf;
#define getchar() (p1==p2&&(p2=(p1=ibuf)+fread(ibuf,1,bufsz,stdin),p1==p2)?EOF:*p1++)
il int read(){
char ch=getchar();
while(ch<'0'||ch>'9'){
ch=getchar();
}
int x=0;
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x;
}
#undef getchar
char obuf[bufsz],*p3=obuf,s[50];
#define flush() (fwrite(obuf,1,p3-obuf,stdout),p3=obuf)
#define putchar(ch) (p3==obuf+bufsz&&flush(),*p3++=(ch))
il void write(ll x){
int top=0;
do{
s[++top]=x%10|48;
x/=10;
}while(x);
while(top){
putchar(s[top--]);
}
}
struct FL{
~FL(){
flush();
}
}fl;
#undef flush
#undef putchar
}
using IO::read;
using IO::write;
const int maxn=3e5+5;
int T,n,m,a[maxn],f[maxn],g[maxn];
il bool check(ll x){
ll sum=0;
priority_queue<int> q;
for(int i=1;i<=n;i++){
if(sum+a[i]<=x){
q.push(a[i]),sum+=a[i];
}
else if(q.size()&&a[i]<q.top()){
sum-=q.top(),q.pop();
q.push(a[i]),sum+=a[i];
}
f[i]=q.size();
}
sum=0;
while(q.size()){
q.pop();
}
for(int i=n;i;i--){
if(sum+a[i]<=x){
q.push(a[i]),sum+=a[i];
}
else if(q.size()&&a[i]<q.top()){
sum-=q.top(),q.pop();
q.push(a[i]),sum+=a[i];
}
g[i]=q.size();
}
f[0]=g[n+1]=0;
for(int i=0;i<=n;i++){
if(f[i]+g[i+1]>=m){
return 1;
}
}
return 0;
}
int main(){
freopen("ace.in","r",stdin);
freopen("ace.out","w",stdout);
n=read(),m=read();
for(int i=1;i<=n;i++){
a[i]=read();
}
ll l=1,r=3e14;
while(l<r){
ll mid=(l+r)>>1;
if(check(mid)){
r=mid;
}
else{
l=mid+1;
}
}
write(l);
return 0;
}
}
int main(){return asbt::main();}
C. 世界
设 \(f_{i,j}\) 表示 \(n-i,k=j\) 的答案,于是有方程:
将 \(f_{i+1,j+1}\) 移项到一边,发现另一边的第二维都 \(\le j\)。但是此时我们不能递推,因为上面那个式子中 \(j\ne1\)。也就是说,我们需要求出 \(j=2\) 的所有 \(f_{i,j}\)。
考虑主元法,将 \(\forall i\in[3,m],f_{i,2}\) 设为主元,做前缀和即可用这 \(m-2\) 个主元表示其它值。注意到我们在表示过程中 \(i\ne m\),而这正好有 \(j\in[2,m-1]\) 共 \(m-2\) 个,于是再用它们的式子高斯消元即可。答案即为 \(f_{n,k}\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=255,mod=1e9+7;
il int pls(int x,int y){
return x+y<mod?x+y:x+y-mod;
}
il int sub(int x,int y){
return x<y?x-y+mod:x-y;
}
il int qpow(int x,int y=mod-2){
int res=1;
while(y){
if(y&1){
res=res*1ll*x%mod;
}
x=x*1ll*x%mod,y>>=1;
}
return res;
}
int n,kk,m,inv[maxn];
struct node{
int a[maxn];
node(){
for(int i=1;i<m;i++){
a[i]=0;
}
}
il int&operator[](int x){
return a[x];
}
il node operator+(node x)const{
node res;
for(int i=1;i<m;i++){
res[i]=pls(a[i],x[i]);
}
return res;
}
il node operator-(node x)const{
node res;
for(int i=1;i<m;i++){
res[i]=sub(a[i],x[i]);
}
return res;
}
il node operator*(int x)const{
node res;
for(int i=1;i<m;i++){
res[i]=a[i]*1ll*x%mod;
}
return res;
}
}a[maxn][maxn],f[maxn][maxn],g[maxn][maxn],h[maxn];
il void gsjd(int n){
for(int i=1;i<=n;i++){
for(int j=1;j<=n+1;j++){
cout<<h[i][j]<<" ";
}
cout<<"\n";
}
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
if(h[j][i]){
swap(h[i],h[j]);
break;
}
}
int t=qpow(h[i][i]);
for(int j=1;j<=n;j++){
if(i==j){
continue;
}
for(int k=i+1;k<=n+1;k++){
h[j][k]=sub(h[j][k],h[j][i]*1ll*t%mod*h[i][k]%mod);
}
}
}
for(int i=1;i<=n;i++){
h[i][n+1]=h[i][n+1]*1ll*qpow(h[i][i])%mod;
}
}
int main(){
freopen("world.in","r",stdin);
freopen("world.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>kk>>m;
if(kk==1||kk==n){
cout<<n;
return 0;
}
inv[1]=1;
for(int i=2;i<=m;i++){
inv[i]=inv[mod%i]*1ll*(mod-mod/i)%mod;
}
for(int i=1;i<=m;i++){
a[i][1][m-1]=a[i][i][m-1]=i;
}
for(int i=3;i<=m;i++){
a[i][2][i-2]=1;
}
for(int j=1;j<=m;j++){
for(int i=j;i<=m;i++){
f[i][j]=f[i-1][j]+a[i][j];
g[i][j]=g[i-1][j-1]+a[i][j];
}
if(j>1){
for(int i=j+1;i<m;i++){
a[i+1][j+1]=a[i][j]*(m*1ll*(i+1)%mod*inv[m-i]%mod*inv[j]%mod)-a[i+1][j]*((i-j+1)*1ll*inv[j]%mod)-(f[i-1][j]+g[i-1][j-1])*(i*1ll*(i+1)%mod*inv[i-1]%mod*inv[m-i]%mod*inv[j]%mod);
}
}
}
for(int i=2;i<m;i++){
h[i-1]=(f[m-1][i]+g[m-1][i-1])*inv[m-1]-a[m][i];
h[i-1][m-1]=(mod-h[i-1][m-1])%mod;
}
gsjd(m-2);
int ans=0;
for(int i=1;i<=m-2;i++){
// cout<<a[n][kk][i]<<" ";
ans=pls(ans,a[n][kk][i]*1ll*h[i][m-1]%mod);
}
// cout<<"\n";
cout<<pls(ans,a[n][kk][m-1]);
return 0;
}
}
int main(){return asbt::main();}
D. 染色
没动。(一语双关

浙公网安备 33010602011771号