8.10XS模拟赛

8.11 A
8.12 BC
8.14 D(完结)

A

luogu P3422

XS评测记1e6的st表超时,洛谷线段树都可以过

于是我气急败坏的写了O(n)的双端队列题解简直就是shi山

接下来是题解:

看到环,很容易想到复制一遍数组把长度变成2n来做

然后做前缀和sum,到达某个点的油量必须大于零,得到sum[i+j]-sum[i-1]>=0(0<=i<n)

变形,得到sum[i+j]>=sum[i-1],所以只要区间内的最小值大于sum[i-1]就可以了

记得顺时针和逆时针都要跑一遍

无脑的最小值可以用st表写,但最优解是双端队列的O(n)虽然没什么用

#include<bits/stdc++.h>
#define usetime() (double)clock () / CLOCKS_PER_SEC * 1000.0
#define fst first
#define sec second
using namespace std;
typedef long long LL;
typedef pair<int,LL> auther;
const int maxn=1e6+5;
void read(int& x){
	char c;
	bool f=0;
	while((c=getchar())<48) f|=(c==45);
	x=c-48;
	while((c=getchar())>47) x=x*10+c-48;
	x=(f ? -x : x);
	return;
}
int n;
int a[maxn],b[maxn];
bool ans[maxn];
LL sum[maxn<<1];
deque<auther> q;
int main(){
	read(n);
	for(int i=1;i<=n;i++){
		read(a[i]),read(b[i]);
	}
	for(int i=1;i<=n;i++){
		sum[i]=sum[i-1]+a[i]-b[i];
	}
	for(int i=1;i<=n;i++){
		sum[n+i]=sum[n+i-1]+a[i]-b[i];
	}
	for(int i=1;i<=n;i++){
		while((!q.empty())&&q.back().sec>=sum[i]) q.pop_back();
		q.push_back(make_pair(i,sum[i]));
	}
	for(int i=1;i<=n;i++){
		while((!q.empty())&&q.front().fst<i) q.pop_front();
		LL mn=q.front().sec;
		if(mn>=sum[i-1]) ans[i]|=1;
		while((!q.empty())&&q.back().sec>=sum[n+i]) q.pop_back();
		q.push_back(make_pair(n+i-1,sum[n+i]));
	}
	q.clear();
	for(int i=n;i>=1;i--){
		sum[n+i]=sum[n+i+1]+a[i]-b[i==1 ? n : i-1];
	}
	for(int i=n;i>=1;i--){
		sum[i]=sum[i+1]+a[i]-b[i==1 ? n : i-1];
	}
	for(int i=2;i<=n+1;i++){
		while((!q.empty())&&q.back().sec>=sum[i]) q.pop_back();
		q.push_back(make_pair(i,sum[i]));
	}
	for(int i=1;i<=n;i++){
		while((!q.empty())&&q.front().fst<i+1) q.pop_front();
		LL mn=q.front().sec;
		if(mn>=sum[n+i+1]) ans[i]|=1;
		while((!q.empty())&&q.back().sec>=sum[n+i+1]) q.pop_back();
		q.push_back(make_pair(n+i+1,sum[n+i+1]));
	}
	for(int i=1;i<=n;i++){
		if(ans[i]) printf("TAK\n");
		else printf("NIE\n");
	}
	return 0;
}
//^o^

8.12 不是这模拟赛连补题都搞心态是吧,调试记录:B C

以下题解均为100pts

B

luogu P3444

有动规的感觉,但不知道怎么动规

观察发现,最优解一定是以删行为主或删列为主的

当发现不能再进行主要操作时,就尝试删左或删右

先假设以删行为主:

设计状态pair类型的dp[li][ri],定义li为起始行,ri为终止行

存储的first表示能删到的最大起始列second为能删到的最小终止列

这样就可以用模拟求每个dp[li][ri]了,lr用于维护行内和列内的前缀和

int &p=f[li][ri].fst,&q=f[li][ri].sec;//已宏定义
while(p<=q&&r[ri][p]-r[li-1][p]<=k) ++p;
while(q>=p&&r[ri][q]-r[li-1][q]<=k) --q;

还有个问题,就是能否删到起始行和终止行呢?

用到动态规划,如果可以删到dp[li-1][ri]或者dp[li][ri+1],并且任意一个能满足能够删掉第li-1行或第ri+1行(行内从其firstsecond

那么dp[li][ri]就可以删到,同时,dp[li][ri]的起始列和终止列也可以继承过来后再跑,可以做到优化,详见代码

此处用first为是否为0来标记能否删到

int &p=f[li][ri].fst,&q=f[li][ri].sec;
if(!p) continue;
while(p<=q&&r[ri][p]-r[li-1][p]<=k) ++p;
while(q>=p&&r[ri][q]-r[li-1][q]<=k) --q;
if(p>q) ans=min(ans,n+m-i);
if(l[li][q]-l[li][p-1]<=k) f[li+1][ri]=f[li][ri];
if(l[ri][q]-l[ri][p-1]<=k) f[li][ri-1]=f[li][ri];

注意初始化dp[1][n]是可以删到的

#include<bits/stdc++.h>
#define fst first
#define sec second
#define mkp(a,b) make_pair(a,b)
#define usetime() (double)clock () / CLOCKS_PER_SEC * 1000.0
using namespace std;
typedef long long LL;
typedef pair<int,int> auther;
const int maxn=2005;
const int inf=2e9+1;
void read(int& x){
	char c;
	bool f=0;
	while((c=getchar())<48) f|=(c==45);
	x=c-48;
	while((c=getchar())>47) x=x*10+c-48;
	x=(f ? -x : x);
	return;
}
int a[maxn][maxn];
LL l[maxn][maxn],r[maxn][maxn];
auther f[maxn][maxn];
int n,m,k;
int main(){
	read(k),read(m),read(n);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			read(a[i][j]);
			l[i][j]=l[i][j-1]+a[i][j];
			r[i][j]=r[i-1][j]+a[i][j];
		}
	}
	int ans=inf;
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j++){
			f[i][j]=mkp(0,m);
		}
	}
	f[1][n]=mkp(1,m);
	for(int i=n;i>=1;i--){
		for(int li=1,ri=i;ri<=n;li++,ri++){
			int &p=f[li][ri].fst,&q=f[li][ri].sec;
			if(!p) continue;
			while(p<=q&&r[ri][p]-r[li-1][p]<=k) ++p;
			while(q>=p&&r[ri][q]-r[li-1][q]<=k) --q;
			if(p>q) ans=min(ans,n+m-i);
			if(l[li][q]-l[li][p-1]<=k) f[li+1][ri]=f[li][ri];
			if(l[ri][q]-l[ri][p-1]<=k) f[li][ri-1]=f[li][ri];
		}
	}
	for(int i=1;i<=m;i++){
		for(int j=i;j<=m;j++){
			f[i][j]=mkp(0,n);
		}
	}
	f[1][m]=mkp(1,n);
	for(int i=m;i>=1;i--){
		for(int li=1,ri=i;ri<=m;li++,ri++){
			int &p=f[li][ri].fst,&q=f[li][ri].sec;
			if(!p) continue;
			while(p<=q&&l[p][ri]-l[p][li-1]<=k) ++p;
			while(q>=p&&l[q][ri]-l[q][li-1]<=k) --q;
			if(p>q) ans=min(ans,n+m-i);
			if(r[q][li]-r[p-1][li]<=k) f[li+1][ri]=f[li][ri];
			if(r[q][ri]-r[p-1][ri]<=k) f[li][ri-1]=f[li][ri];
		}
	}
	printf("%d",ans);
	return 0;
}
//^o^

C

luogu P3426

离场A最近的一次反正就是没写出来TT

看到字符串匹配想到KMP,KMP没用但是前缀数组有用

一个字符串的答案要么是它本身,要么是其前后缀匹配部分(就是KMP中的前缀数组所对的字符串)的前缀

image

如图例如红框中的字符串,可能的答案为AABABCDAB(当然这里只有ABCDAB是正确的)

那难道要去枚举吗?不,发现如果是子串部分,可以通过去求这一部分的答案,再推出原来的字符串

这不就是动规吗?设计状态dp[i]表示原字符串长度为i前缀

(以下用数字表示字符串均表示原字符串长度为该数字的前缀,因为答案一定是原字符串的某个前缀,pi表示的前缀数组)

因为1 -> pi[i]i-pi[i]+1 -> i是一样的(前缀数组的定义),所以只要i-pi[i]+1 -> i区间内存在一个前缀也满足能够被pi[i]的答案组成就好了

我们只需要判断是否有下标j满足j+pi[i]>=i且字符串j的答案和i的答案pi[i]相同即可

转变条件,得到j>=i-pi[i]&&dp[j]==dp[pi[i]],用t[i]保存一下满足dp[j]==i中的最大的j,时间复杂度为O(n)

#include<bits/stdc++.h>
#define usetime() (double)clock () / CLOCKS_PER_SEC * 1000.0
using namespace std;
typedef long long LL;
const int maxn=5e5+5;
void read(int& x){
	char c;
	bool f=0;
	while((c=getchar())<48) f|=(c==45);
	x=c-48;
	while((c=getchar())>47) x=x*10+c-48;
	x=(f ? -x : x);
	return;
}
char s[maxn];
int pi[maxn];
int dp[maxn];
int t[maxn];
int main(){
	scanf("%s",s+1);
	int n=strlen(s+1);
	for(int i=2,j=1;i<=n;i++){
		while(j>1&&s[i]!=s[j]) j=pi[j-1]+1;
		if(s[i]==s[j]) ++j;
		pi[i]=j-1;
	}
	dp[1]=1,t[1]=1;
	for(int i=2;i<=n;i++){
		if(t[dp[pi[i]]]>=i-pi[i]) dp[i]=dp[pi[i]];
		else dp[i]=i;
		t[dp[i]]=i;
	}
	printf("%d",dp[n]);
	return 0;
}
//^o^

D

luogu P3436

建反图,然后从主楼开始跑拓扑,状态转移方程为f[v]=min(f[v]+f[u],inf+1)

跑拓扑之前要排除掉所有主楼不能到达的点,具体先跑一遍DFS

如果图中存在环,那么答案为无数种,由于拓扑排序是不会处理环上的点的

所以在拓扑跑完以后判断是否有节点主楼能够到达,但是拓扑没有处理,那么这些点就是在环上了

仔细观察题目“每栋楼都可以访问任意多次”,说明主楼也可以经过,那么需要特判主楼在环上的情况

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5,maxm=1e6+5;
const int inf=36500;
void read(int& x){
	char c;
	bool f=0;
	while((c=getchar())<48) f|=(c==45);
	x=c-48;
	while((c=getchar())>47) x=x*10+c-48;
	x=(f ? -x : x);
	return;
}
class mmp{
private:
	int mp_cnt;
public:
	int head[maxn],nxt[maxm],e[maxn];
	void init_mp(){
		memset(head,-1,sizeof(head));
		mp_cnt=-1;
	}
	void add_edge(int u,int v){
		e[++mp_cnt]=v;
		nxt[mp_cnt]=head[u];
		head[u]=mp_cnt;
	}
}mp;
int n,m;
int in[maxn];
int f[maxn];
bool in_mp[maxn],vis[maxn];
int ans=0;
bool tp=0;
void dfs(int u){
	in_mp[u]=1;
	for(int i=mp.head[u];~i;i=mp.nxt[i]){
		int v=mp.e[i];
		++in[v];
		if(in_mp[v]){
			if(v==n+1) tp=1;
			continue;
		}
		dfs(v);
	}
}
int main(){
	read(n),read(m);
	int u,v;
	mp.init_mp();
	for(int i=1;i<=m;i++){
		read(u),read(v);
		mp.add_edge(v,u);
	}
	dfs(n+1);
	vector<int> ans;
	if(tp){//特判主楼在环上
		printf("zawsze\n");
		for(int i=1;i<=n;i++){
			if(in_mp[i]) ans.push_back(i);
		}
		printf("%d\n",ans.size());
		for(int i=0;i<ans.size();i++){
			printf("%d ",ans[i]);
		}
		return 0;
	}
	queue<int> q;
	int mx=0;
	f[n+1]=1;
	q.push(n+1);
	while(!q.empty()){
		u=q.front();
		q.pop();
		vis[u]=1,mx=max(mx,f[u]);
		for(int i=mp.head[u];~i;i=mp.nxt[i]){
			v=mp.e[i];
			f[v]=min(f[v]+f[u],inf+1);
			--in[v];
			if(!in[v]) q.push(v);
		}
	}
	for(int i=1;i<=n;i++){
		if((in_mp[i]&&(!vis[i]))||f[i]>inf){
			ans.push_back(i);
		}
	}
	if(ans.size()){
		printf("zawsze\n");
	}
	else{
		for(int i=1;i<=n+1;i++){
			if(mx==f[i]) ans.push_back(i);
		}
		printf("%d\n",mx);
	}
	printf("%d\n",(int)ans.size());
	sort(ans.begin(),ans.end());
	for(int i=0;i<ans.size();i++){
		printf("%d ",ans[i]);
	}
	return 0;
}
//^o^
posted @ 2025-08-11 21:03  huangems  阅读(16)  评论(0)    收藏  举报