10.31 模擬賽題解

summ1t發表於2024-11-01

T1:P1725 琪露諾

簡單 \(\text{dp}\) 題,狀態轉移很好寫:\(f_i=\max_{j=i-R}^{i-L}f_j+a_i\)

暴力做是 \(O(n^2)\) 的,考慮最佳化。

發現每次查詢一個區間內的 \(f\) 最大值,直接上線段樹即可,時間複雜度 \(O(nlogn)\)

還可以單調佇列最佳化,時間複雜度線性。

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
#define rd read()
#define ll long long
#define FOR(i,j,k) for(int i=j;i<=k;i++)
#define ROF(i,j,k) for(int i=j;i>=k;i--)
int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return x*f;
}
const int N=4e5+10,INF=2e9;
int n,L,R,a[N],f[N],mx[N<<2];
void pushup(int u){mx[u]=max(mx[u<<1],mx[u<<1|1]);}
void modify(int u,int l,int r,int x,int v){
	if(l==r){mx[u]=v;return;}
	int mid=(l+r)>>1;
	if(x<=mid) modify(u<<1,l,mid,x,v);
	else modify(u<<1|1,mid+1,r,x,v);
	pushup(u);
}
int query(int u,int l,int r,int ql,int qr){
	if(ql<=l&&r<=qr) return mx[u];
	int mid=(l+r)>>1,ans=-INF;
	if(ql<=mid) ans=max(ans,query(u<<1,l,mid,ql,qr));
	if(qr>mid) ans=max(ans,query(u<<1|1,mid+1,r,ql,qr));
	return ans;
}
int main(){
	n=rd,L=rd,R=rd;n++;
	FOR(i,1,n) a[i]=rd;
	memset(mx,-0x3f,sizeof(f)),f[1]=0;
	modify(1,1,2*n,1,0);
	FOR(i,1,n+R){
		if(i-L<=0) continue;
		f[i]=max(f[i],query(1,1,2*n,max(i-R,1),i-L)+a[i]);
		modify(1,1,2*n,i,f[i]);
	}
	int ans=-INF;
	FOR(i,n,n+R) ans=max(ans,f[i]);
	printf("%d\n",ans);
	return 0;
}

T2:P8346 「Wdoi-6」最澄澈的空與海

顯然,一張二分圖若要有恰好 \(1\) 個完美匹配,必須滿足存在入度為 \(1\) 的節點。

但這還不夠,上面這倆不是充要條件,反例很容易舉出。

發現入度為 \(1\) 的點是很重要的,從它入手縮小問題規模。

對於入度為 \(1\) 的點,我們可以找到它對面與它唯一相連的節點,此時這兩個點被鎖死了。所以它對面的那個點,除了和它相連的
邊,其他都無用了,可以刪去。

所以我們要維護所有入度為 \(1\) 的點,模擬上述操作。只要最終所有點刪完,即二分圖恰有 \(1\) 個完美匹配。

可以用拓撲排序的思想來做,儲存入度為 \(1\) 的點。

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
#define rd read()
#define ll long long
#define FOR(i,j,k) for(int i=j;i<=k;i++)
#define ROF(i,j,k) for(int i=j;i>=k;i--)
#define debug cout<<"ILOVECCF!"<<endl;
int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return x*f;
}
const int N=4e6+10;
int T,n,m,d[N],del[N];
vector<int> e[N];
bool toposort(){
	memset(del,0,sizeof(del));
	queue<int> q;int cnt=0;
	FOR(i,1,2*n) if(d[i]==1) q.push(i);
	while(!q.empty()){
		int t=q.front();q.pop();
		if(del[t]) continue;del[t]=1,cnt++;
		int v;
		for(auto y:e[t]){
			if(!del[y]){v=y;break;}
		}
		if(!v) return false;
		del[v]=1,cnt++;
		for(auto y:e[v]){
			if(!del[y]&&--d[y]==1) q.push(y);
		}
	}
	return cnt==2*n;
}
int main(){
	T=rd;
	while(T--){
		FOR(i,1,2*n) e[i].clear();memset(d,0,sizeof(d));
		n=rd,m=rd;
		FOR(i,1,m){
			int x=rd,y=rd;y+=n;
			d[x]++,d[y]++;
			e[x].push_back(y),e[y].push_back(x);
		}
		if(toposort()) puts("Renko");
		else puts("Merry");
	}
	return 0;
}

T3:P4514 上帝造題的七分鐘

給你一個矩陣,子矩陣加、求子矩陣和,二維樹狀陣列模版題。

類比一維樹狀陣列維護區間加、區間和的思路即可,計算每個位置的出現次數。

最後需要開 \(4\) 個樹狀陣列,區間加就用差分的思想,區間求和就用字首和思想。

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
#define rd read()
#define ll long long
#define FOR(i,j,k) for(int i=j;i<=k;i++)
#define ROF(i,j,k) for(int i=j;i>=k;i--)
int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return x*f;
}
const int N=3000;
int n,m,q;ll tr1[N][N],tr2[N][N],tr3[N][N],tr4[N][N];
int lowbit(int x){return x&-x;}
void add(int x,int y,int v,ll tr[][N]){
	for(int i=x;i<=n;i+=lowbit(i))
		for(int j=y;j<=m;j+=lowbit(j))
			tr[i][j]+=v;
}
ll query(int x,int y,ll tr[][N]){
	ll res=0;
	for(int i=x;i;i-=lowbit(i))
		for(int j=y;j;j-=lowbit(j))
			res+=tr[i][j];
	return res;
}
void modify(int x,int y,int v){
   add(x,y,v,tr1),add(x,y,v*x,tr2),add(x,y,v*y,tr3),add(x,y,v*x*y,tr4);
}
ll getsum(int x,int y){
    return (x+1)*(y+1)*query(x,y,tr1)-(y+1)*query(x,y,tr2)-(x+1)*query(x,y,tr3)+query(x,y,tr4);
}
int main(){
	char ch;cin>>ch;n=rd,m=rd;
	while(cin>>ch){
		int a=rd,b=rd,c=rd,d=rd,v;
		if(ch=='k'){
			v=rd;
			modify(a,b,v),modify(c+1,d+1,v),modify(c+1,b,-v),modify(a,d+1,-v);
		}
		else{
			printf("%lld\n",getsum(c,d)-getsum(a-1,d)-getsum(c,b-1)+getsum(a-1,b-1));
		}
	}
	return 0;
}

T4:P7838 「Wdoi-3」夜雀 treating

貪心+線段樹

首先模擬一下題中所述過程,發現是先把序列中點選出來,然後它的左右兩邊劃分為兩個數列,分別設為 \(A,B\)

接下來我們每次操作,選出一個數列最後的一個數,然後再另一個數列中刪去一個數。

考慮暴力去做,可以先列舉答案區間,然後我們選擇在區間的數,刪去不在區間的最末端的數。這樣做是最優的。

時間複雜度 \(O(n^3)\),考慮最佳化。

容易發現,對於區間 \([l,r]\),如果它裡面的數能被滿足,那麼 \([l+1,r],[l,r-1]\) 也能滿足。對於這樣的經典性質,可以直接雙指標最佳化,時間複雜度降為 \(O(n^2)\)

還要繼續最佳化,我們發現每次列舉答案區間,都要暴力的去模擬一遍上述過程,這是不可接受的。但發現每次移動區間左右端點,只會令某個數是否選擇發生改變。

假如要選 \(A\) 中的數,發現 \(B\) 的所有字尾中,不在區間內的數必須小於等於相應的 \(A\) 的字尾內在區間內的數。

所以對於每個位置,維護 \(A\) 字尾與 \(B\) 字尾的差,滿足全域性最小值 \(<0\),則區間滿足條件。

直接上線段樹就好了,區間修改,全域性最小值。

時間複雜度 \(O(nlogn)\)

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
#define rd read()
#define ll long long
#define FOR(i,j,k) for(int i=j;i<=k;i++)
#define ROF(i,j,k) for(int i=j;i>=k;i--)
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*f;
}
const int N=4e5+10;
int n,a[N],pos[N],mn[N<<2],tag[N<<2];
void pushup(int u){mn[u]=min(mn[u<<1],mn[u<<1|1]);}
void pushdown(int u){
	tag[u<<1]+=tag[u],tag[u<<1|1]+=tag[u];
	mn[u<<1]+=tag[u],mn[u<<1|1]+=tag[u];
	tag[u]=0;
}
void build(int u,int l,int r){
	if(l==r){mn[u]=l;return;}
	int mid=(l+r)>>1;
	build(u<<1,l,mid),build(u<<1|1,mid+1,r),pushup(u);
}
void modify(int u,int l,int r,int ql,int qr,int v){
	if(ql<=l&&r<=qr){mn[u]+=v,tag[u]+=v;return;}
	pushdown(u);int mid=(l+r)>>1;
	if(ql<=mid) modify(u<<1,l,mid,ql,qr,v);
	if(qr>mid) modify(u<<1|1,mid+1,r,ql,qr,v);
	pushup(u);
}
int main(){
	n=rd;FOR(i,1,2*n+1) a[i]=rd,pos[a[i]]=i;
	int l=1,r=0,ans=0;build(1,1,n);
	while(r<=2*n+1){
		if(mn[1]<0){
			l++;
			if(pos[l-1]==n+1) continue;
			if(pos[l-1]<n+1) modify(1,1,n,pos[l-1],n,1);
			else modify(1,1,n,2*n+2-pos[l-1],n,1);
		}
		else{
			ans=max(ans,r-l+1),r++;
			if(pos[r]==n+1) continue;
			if(pos[r]<n+1) modify(1,1,n,pos[r],n,-1);
			else modify(1,1,n,2*n+2-pos[r],n,-1);
		}
	}
	printf("%d\n",ans);
	return 0;
}