2021 10.19 模擬測試
\(\mathrm{T1}\)
\(\mathrm{Solution}\)
直接\(dp\) \(\mathrm{60pts}\)
正解和卡特蘭數有關係。
假設遠離家中為1,走進家中為0
其第一步一定為1,加下來的行動中,從前到後任意位置1的個數必然大於等於0的個數(不考慮第一個1),顯然這類似卡特蘭數
用折線發不難證明其方案數為\(\binom{n+m}{n}−\binom{n+m}{ n+1}\)
#include<bits/stdc++.h> #define int long long #define in read() using namespace std; inline int read() { int data=0,w=1; char ch=getchar(); while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar(); if(ch=='-') w=-1,ch=getchar(); while(ch>='0'&&ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar(); return data*w; } inline void write(int x) { if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); } const int N=5e6+5; const int mod=998244353; int n,k,fac[N],inv[N],mi2[N],ans=0; inline int dec(int a,int b){return a-b<0?(a-b+mod)%mod:a-b;} inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;} inline int mul(int a,int b){return 1ll*a*b%mod;} inline int qpow(int a,int b) { int ret=1; while(b) { if(b&1) ret=mul(ret,a); a=mul(a,a);b>>=1; } return ret; } int C(int n,int m){return mul(fac[n],mul(inv[m],inv[n-m]));} signed main() { // freopen("home.in","r",stdin); // freopen("home.out","w",stdout); n=in,k=in; if(n>k){puts("0");return 0;;} int tmp=mul(1,qpow(2,mod-2)); fac[0]=inv[0]=mi2[0]=1; for(int i=1;i<=k;i++) { fac[i]=mul(fac[i-1],i); mi2[i]=mul(mi2[i-1],tmp); } inv[k]=qpow(fac[k],mod-2); for(int i=k-1;i;--i) inv[i]=mul(inv[i+1],i+1); for(int i=n;i<=k;i+=2) ans=add(ans,mul(dec(C(i-1,n-1+(i-n)/2),C(i-1,n-1+(i-n)/2+1)),mi2[i])); write(ans); }
\(\mathrm{T2}\)
\(\mathrm{Solution}\)
如果 \(\mathrm{k|n}\) ,那麼把序列分成 k 組,每組\(\frac nk\)項
答案就是所有組最大項和最小項之差
構造方案如下:
每隔 k 個按照大小順序放置一組內所有的元素。
答案顯然同上
在這種情況下,顯然將元素從小到大分組為最優答案
如果不是,任然把序列分為 k 組,有\(n\mod k\) 組為\(\frac nk+1\),其餘為\(\frac nk\),
記\(dp[i][j]\)為選取了\(i\)個\(\frac nk+1\),\(j\)個\(\frac nk\)的最小答案
轉移即可
#include<bits/stdc++.h> #define int long long #define in read() using namespace std; inline int read() { int data=0,w=1; char ch=getchar(); while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar(); if(ch=='-') w=-1,ch=getchar(); while(ch>='0'&&ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar(); return data*w; } inline void write(int x) { if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); } const int N=1e6+5; int n,k; int a[N],dp[2][N]; signed main() { n=in,k=in; for(int i=1;i<=n;i++) a[i]=in; sort(a+1,a+n+1); int q=n/k; int r1=n%k,r2=k-r1; int q1=q+1,q2=q; for(int i=1;i<=r2;i++) dp[0][i]=a[i*q2]-a[q2*(i-1)+1]+dp[0][i-1]; for(int i=1;i<=r1;i++) { dp[i&1][0]=dp[i&1^1][0]+a[i*q1]-a[q1*(i-1)+1]; for(int j=1;j<=r2;++j) { int idx=i*q1+j*q2; dp[i&1][j] = min( dp[i&1][j-1]+a[idx]-a[idx-q2+1] , dp[i&1^1][j] + a[idx]-a[idx-q1+1]); } } write(dp[r1&1][r2]); }
\(\mathrm{T3}\)
\(\mathrm{Solution}\)
我們首先需要對問題進行轉化
發現直接求得答案較為困難,且其具有單調性,我們考慮二分答案
假設集合是靜態的(沒有插入操作)
我們當前的答案為mid,大於等於mid的元素有x個,小於mid的元素的和為sum
當且僅當滿足\(sum\ge (c−x)∗mid\),存在至少操作mid次的方案
證明如下:
原問題等價於向mid個集合中每個填充c個元素
每個集合中不能出現重複元素
第i個元素可以填充\(a_i\)次
顯然每個元素最多使用\(min(mid,a_i)\)次
對於大於等於\(mid\)的元素,其可使用\(mid\)次,總計\(x∗mid\)
對於小於\(mid\)的元素,其可使用\(\sum a_i\)次,總計\(sum\)
證畢
當集合是動態時
我們需要一種資料結構維護大於等於某個元素的元素個數和小於等於其的元素的和
因為插入操作僅修改一個元素
且善良的出題人沒有強制線上
樹狀陣列即可維護這些資訊
#include<bits/stdc++.h>
#define int long long
#define in read()
using namespace std;
inline int read()
{
int data=0;int w=1; char ch=0;
ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return data*w;
}
inline void write(int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int N=1e6+5;
int n,tot,cnt,num,b[N];
struct node{
int opt,x;
}a[N];
struct tree{
int t1[N],t2[N];
inline int lowbit(int x){return x&(-x);}
inline void update(int k,int val){for(;k<=cnt;k+=lowbit(k)) t1[k]++,t2[k]+=val;}
inline int query(int x)
{
if(x>num) return 0;
int sum=0,mid=0,cur=0;
for(int i=20;i>=0;--i)
{
if(cur+(1<<i)>cnt) continue;
if((x-(num-sum-t1[cur+(1<<i)]))*b[cur+(1<<i)]<=mid+t2[cur+(1<<i)])
{
cur+=(1<<i);
sum+=t1[cur],mid+=t2[cur];
}
}
return mid/(x+sum-num);
}
}t;
signed main()
{
// freopen("set3.in","r",stdin);
// freopen("soc.out","w",stdout);
n=in;
for(int i=1;i<=n;i++) a[i].opt=in,a[i].x=in;
for(int i=1;i<=n;i++) if(a[i].opt==1) b[++tot]=a[i].x;
sort(b+1,b+tot+1);
cnt=unique(b+1,b+tot+1)-b-1;
for(int i=1;i<=n;i++) if(a[i].opt==1) a[i].x=lower_bound(b+1,b+cnt+1,a[i].x)-b;
for(int i=1;i<=n;i++)
{
if(a[i].opt==1) t.update(a[i].x,b[a[i].x]),num++;
else write(t.query(a[i].x)),puts("");
}
}
\(\mathrm{T4}\)
\(\mathrm{Solution}\)
顯然這道題可以分為兩個部分
第一步,對於每一支軍隊,確定其能獲得最大的利益
第二步,確定軍隊在約束條件下獲得的最大利益和
我們先考慮第一步
顯然可以使用弗洛伊德演算法求出任意兩點間的距離
由於城堡可以被攻破多次,顯然對於處於同一地點的城堡,如果其收益小於等於其他城堡且防禦力大於等於該城堡,則其一定不會被選中
如此我們便可以使得一個地點的所有城堡按照防禦力和收益同時遞減的方式排列
同樣地,我們令一個地點的軍隊按照攻擊力遞減排列
不難發現,對於處於同一地點的軍隊,他們所匹配的城堡一定是單調的
所以對於處於同一地點的軍隊,我們最多訪問所有軍隊1次
對於每個軍隊,我們最多訪問每個地點一次
所以總時間複雜度為O(ns)
對於第二步
我們先把答案設定為所有收益為正的軍隊的收益和
我們把所有含有依賴關係的軍隊分為兩組,第一組的所有軍隊收益為正
第二組的所有軍隊收益為負
對於第一組另原點向其連結容量為收益的邊
對於第二組另原點向其連結容量為收益相反數的邊
對於所有依賴關係,依賴軍隊向其被依賴軍隊連結容量無窮的邊
減去當前圖的最小割即為最終答案
#include<bits/stdc++.h>
#define int long long
#define pb push_back
#define in read()
using namespace std;
inline int read()
{
int data=0,w=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0'&&ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return data*w;
}
inline void write(int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int N=2e5+10;
const int inf=1e17;
int n,m,s,b,t,ans,tmp;
int dis[110][110],use[N],f[N];
int first[N],cnt=1,S,T,dep[N],cur[N];
struct army{int a,f,p,id,val;bool flag;};
struct castle{int d,g;};
struct edge{int v,flow,nxt;}e[N<<1];
vector<army>ar[110];
vector<castle>cs[110],css[110];
inline void add(int u,int v,int w){e[++cnt]=(edge){v,w,first[u]};first[u]=cnt;}
bool cmp1(army a,army b){return a.a<b.a;}
bool cmp2(castle a,castle b){return a.d==b.d?a.g<b.g:a.d<b.d;}
void floyd()
{
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
bool bfs()
{
queue<int>q;
for(int i=0;i<=s+1;i++) dep[i]=-1;
q.push(S);dep[S]=1;
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=first[u];i;i=e[i].nxt)
{
int v=e[i].v;
if(dep[v]==-1&&e[i].flow)
{
dep[v]=dep[u]+1;
if(v==T) return 1;
cur[v]=first[v];
q.push(v);
}
}
}
return 0;
}
int dinic(int u,int ex)
{
if(u==T) return ex;
int flow=0;
for(int &i=cur[u];i;i=e[i].nxt)
{
int v=e[i].v;
if(e[i].flow&&dep[v]==dep[u]+1)
{
int k=dinic(v,min(ex-flow,e[i].flow));
if(k)
{
e[i].flow-=k;
e[i^1].flow+=k;
flow+=k;
if(flow==ex) return ex;
}
}
}
dep[u]=0;
return flow;
}
signed main()
{
n=in,m=in;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)dis[i][j]=inf;
for(int i=1;i<=m;i++)
{
int u=in,v=in;
dis[u][v]=dis[v][u]=1;
}
for(int i=1;i<=n;i++) dis[i][i]=0;
floyd();
s=in,b=in,t=in;
for(int i=1;i<=s;i++)
{
int x=in,a=in,f=in,p=in;
ar[x].pb((army){a,f,p,i,0,0});
}
for(int i=1;i<=b;i++)
{
int x=in,d=in,g=in;
cs[x].pb((castle){d,g});
}
for(int i=1;i<=n;i++)sort(ar[i].begin(),ar[i].end(),cmp1);
for(int i=1;i<=n;i++)sort(cs[i].begin(),cs[i].end(),cmp2);
for(int i=1;i<=n;i++)
for(int j=0;j<cs[i].size();j++)
{
while(j<cs[i].size()&&css[i].size()&&cs[i][j].g<css[i][css[i].size()-1].g) j++;
if(j>=cs[i].size()) break;
css[i].pb(cs[i][j]);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(!css[j].size()) continue;
int l=-1;
for(int k=0;k<ar[i].size();k++)
{
while(l+1<css[j].size()&&css[j][l+1].d<=ar[i][k].a) l++;
if(l!=-1&&ar[i][k].f>=dis[i][j])
ar[i][k].flag=1,ar[i][k].val=max(ar[i][k].val,css[j][l].g);
}
}
for(int i=1;i<=n;i++)
for(int j=0;j<ar[i].size();j++)
{
use[ar[i][j].id]=ar[i][j].flag;
if(use[ar[i][j].id]) f[ar[i][j].id]=ar[i][j].val-ar[i][j].p;
else f[ar[i][j].id]=-inf;
ans+=max((long long)0,f[ar[i][j].id]);
}
int S=0,T=s+1;
for(int i=1;i<=t;i++)
{
int u=in,v=in;
add(u,v,inf);add(v,u,0);
}
for(int i=1;i<=s;i++)
{
if(f[i]>0) add(S,i,f[i]),add(i,S,0);
else if(f[i]<0) add(i,T,-f[i]),add(T,i,0);
}
while(bfs()) while(tmp=dinic(S,inf)) ans-=tmp;
write(ans);
}