10.31 模拟赛题解

T1:P1725 琪露诺

简单 dp 题,状态转移很好写:fi=maxj=iRiLfj+ai

暴力做是 O(n2) 的,考虑优化。

发现每次查找一个区间内的 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(n3),考虑优化。

容易发现,对于区间 [l,r],如果它里面的数能被满足,那么 [l+1,r],[l,r1] 也能满足。对于这样的经典性质,可以直接双指针优化,时间复杂度降为 O(n2)

还要继续优化,我们发现每次枚举答案区间,都要暴力的去模拟一遍上述过程,这是不可接受的。但发现每次移动区间左右端点,只会令某个数是否选择发生改变。

假如要选 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;
}
posted @   summ1t  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示