做题笔记(一)

Sort Left and Right

题目传送门

Sort Left and Right

题目大意

给定关于 N 的排列 P,定义一个操作为选择一个 k(1kN),然后把 [1,k1][k+1,N] 都按升序排序。求讲 P 变成一个从 1N 的序列的最小操作次数。带多测。

N2×105

思路

其实是一个小结论题,赛时智障写了 30min。

首先发现,如果 P 本身就是排好序的,那么输出 0 即可。

然后可以考虑遍历一下 P,选取 k,判断是否可以在 1 次操作内完成。如果可以的话就输出 1

如果 1 次操作不行的话,那么考虑存在一种排列 {a,b,,1},显然我们可以先选取 k=1,把 [2,N] 排好序,然后在选取一个 k 满足 3kN 就可以在两步之内完成。由这个排列可以推广到几乎所有情况,那么最优解就是 2

值得一提的是,当排列为 {N,a,b,,1} 时,最优解只能是 3

时间复杂度 O(N)

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n,a[200001];
bool v[200001]={0};
int pos;

void solve(){
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i];

	pos=0;
	for(int i=1;i<=n;i++)
		v[i]=0;

	bool f=1;
	for(int i=1;i<=n;i++){
		if(a[i]!=i){
			f=0;
			break;
		}
	}

	if(f){
		cout<<0<<endl;
		return;
	}
	
	if(a[1]==n&&a[n]==1){
		cout<<3<<endl;
		return;
	}

	for(int i=1;i<=n;i++){
		while(v[pos+1]&&pos<n)
			pos++;

		if(a[i]==i){
			if(pos>=i-1){
				cout<<1<<endl;
				return;
			}
		}

		v[a[i]]=1;
	}

	cout<<2<<endl;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);

	int t; cin>>t;
	while(t--) solve();

	return 0;
}

Distinct Characters Queries

题目传送门

Distinct Characters Queries

题目大意

给定一个字符串 s,有 q 次操作。

  • 给出 i,c,将 si 改成 c

  • 给出 l,r,求 [l,r] 中不同字符的种类个数。

|s|,q2×105

思路

其实挺水的,但是考场上不一定会写这种做法。

给出一个数组 cnti,j 满足 1i|s|,1j26 表示前 i 位中拥有字符 j 的个数,可以 O(|s|) 预处理。

对于每一次更改,就是把 cnti~|s|,si 都减 1,同样地,把 cnti~|s|,c 都加 1

显然地,区间加单点查,线段树和树状数组都可以做。

这时肯定很多人就想这道题用数据结构肯定不是正解!一定有更优秀的解法。

其实不是的,这道题的正解就是数据结构。

时间复杂度 O(|s|+qlog|s|)

代码

代码没写咕咕咕。

满汉全席

题目传送门

满汉全席

题目大意

题意过长无法概括咕咕咕。

思路

这应该可以算省选送分题。

根据所有评审员的要求(限制),我们发现这是一个非常板的 2-SAT。每一个评审员的要求至少要满足一个。

所以我们可以把每一次给出的要求这样连边:

  • 如果两个都是 m,那么就把 a+n,b 连起来。

以此类推,我们得到了 2×2=4 种连边方式,然后跑 2-SAT 就好了。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n,m,tot=0;
vector<int>V[1000001];
int dfn[1000001]={0},low[1000001]={0};
int timer=0,belong[1000001]={0};
stack<int>s;bool instack[1000001]={0};

void tarjan(int p){
	dfn[p]=++timer;low[p]=timer;
	s.push(p);instack[p]=1; 
	
	for(int i=0;i<V[p].size();i++){
		if(!dfn[V[p][i]]){
			tarjan(V[p][i]);
			low[p]=min(low[V[p][i]],low[p]);
		}
		else if(instack[V[p][i]])
			low[p]=min(dfn[V[p][i]],low[p]);
	}
	
	if(dfn[p]==low[p]){
		tot++;
		while(s.top()!=p){
			instack[s.top()]=0;
			belong[s.top()]=tot;
			s.pop();
		}
		instack[s.top()]=0;
		belong[s.top()]=tot;
		s.pop();
	}
}

void solve(){
	cin>>n>>m;
	
	timer=0;tot=0;
	while(!s.empty())s.pop();
	for(int i=1;i<=100000;i++)V[i].clear();
	for(int i=1;i<=100000;i++)
		dfn[i]=0,low[i]=0,belong[i]=0,instack[i]=0;
	
	for(int i=1;i<=m;i++){
		char x,y;int a,b;
		cin>>x>>a>>y>>b;
		
		if(x=='m'&&y=='m'){
			V[a+n].push_back(b);
			V[b+n].push_back(a);
		}
		
		else if(x=='m'&&y=='h'){
			V[a+n].push_back(b+n);
			V[b].push_back(a);
		}
		
		else if(x=='h'&&y=='m'){
			V[a].push_back(b);
			V[b+n].push_back(a+n);
		}
		
		else{
			V[a].push_back(b+n);
			V[b].push_back(a+n);
		}
	}
	
	for(int i=1;i<=(n<<1);i++)
		if(!dfn[i])tarjan(i);
		
	for(int i=1;i<=n;i++){
		if(belong[i]==belong[i+n]){
			cout<<"BAD"<<endl;
			return;
		}
	}
	
	cout<<"GOOD"<<endl;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	int t;cin>>t;
	while(t--)solve();
	
	return 0;
}

试卷(Problem)

题目传送门

没有原题咕咕咕。

题目大意

给出 N×M 矩阵,每一个块上面是 1/2/3/,要求把这个矩阵划分成若干个子矩阵,要求:

  • 子矩阵种没有

  • 子矩阵中数字的平均数为 2

N×M2000

思路

发现,13 的平均数就是 2。用贪心算法显然可以发现每一个块中的 13 的数量是越少越好,所以只需要考虑相邻的 13

发现,每一个 1 都需要找一个 3 来匹配,很容易想到二分图匹配中的匈牙利算法。

首先把所有的 3 位置编号,然后对于每一个 1,都与其相邻的 3 连边,然后跑匈牙利即可。

时间复杂度 O(n3)

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n,m,ct=0,c3=0;
int cnt=0;
int vis[2001000]={0};
int mch[2001000]={0};

vector<int>V[100005];

bool dfs(int p,int tag){
	if(vis[p]==tag) return 0;
	
	vis[p]=tag;
	
	for(int i=0;i<V[p].size();i++){
		int to=V[p][i];
		
		if((mch[to]==0)||dfs(mch[to],tag)){
			mch[to]=p;
			
			return 1;
		}
	}
	
	return 0;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n>>m;
    int a[n+2][m+2];
    char c[n+2][m+2];
	
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>c[i][j];
			
			if(c[i][j]=='3')
				a[i][j]=++c3;
		}
	}
	
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++){
			if(c[i][j]=='2')
				ct++;
			
			if(c[i][j]=='1'){
				cnt++;
				
				if(c[i-1][j]=='3') V[cnt].push_back(a[i-1][j]);
				if(c[i+1][j]=='3') V[cnt].push_back(a[i+1][j]);
				if(c[i][j+1]=='3') V[cnt].push_back(a[i][j+1]);
				if(c[i][j-1]=='3') V[cnt].push_back(a[i][j-1]);
			}
		}
	
	int ans=0;
	for(int i=1;i<=cnt;i++){
		if(dfs(i,i)) ans++;
	}
	
	cout<<ans+ct<<endl;
	
	return 0;
}

AtCoder Regular Contest001

センター採点

题目传送门

センター採点

题目大意

给出字符串 s,求 s 中出现最多的字符次数和最少字符次数。

思路

开一个桶直接跑就行,远古 arc 入门题。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	int n,num[5]={0}; cin>>n;
	string s; cin>>s;
	
	for(int i=0;i<n;i++){
		num[s[i]-'0']++;
	}
	
	int mx=0,mn=1e17;
	for(int i=1;i<=4;i++){
		mx=max(num[i],mx);
		mn=min(num[i],mn);
	}
	
	cout<<mx<<' '<<mn<<endl;
	
	return 0;
}

リモコン

题目传送门

リモコン

题目大意

给出 a,b,每次给 a11551010,求 a 变成 b 最小步数。

0a,b40

思路

估计会有人想成贪心吧,其实不是的。

比如说下面这个数据:19,28,如果用贪心来写的话答案是 5,而正确答案是 2。所以我们考虑 DP。

不妨设 a<b,设 fi 表示从 ai 的最小操作次数,显然可以有以下转移方程:

  • 首先对于 i>a,有 fi=min{fi1,fi5,fi10}+1

  • 对于第二次的 DP,我们可以假定一个上界。注意到数据范围很小,那么我们定这个上界为 100,有 fi=min{fi+1+1,fi+5+1,fi+10+1,fi}

跑得挺快的。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	int a,b; cin>>a>>b;
	if(a>b) swap(a,b);
	int f[500]={0}; f[a]=0;

	for(int i=a+1;i<=100;i++){
		f[i]=f[i-1]+1;
		if(i-a>=5) f[i]=min(f[i-5]+1,f[i]);
		if(i-a>=10) f[i]=min(f[i-10]+1,f[i]);
	}

	for(int i=99;i>=a;i--){
		f[i]=min(f[i+1]+1,f[i]);
		if(100-i>=5) f[i]=min(f[i+5]+1,f[i]);
		if(100-i>=10) f[i]=min(f[i+10]+1,f[i]);
	}

	cout<<f[b]<<endl;
	
	return 0;
}

パズルのお手伝い

题目传送门

パズルのお手伝い

题目大意

给一个已经填了 5 个皇后的八皇后残局,求这个残局能否填完八皇后,并且输出填的方案。

思路

八皇后啊,搜索经典板题。

DFS 即可,不知道为什么评

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;

bool vis[15][15]={0};
char c[15][15]={0};

void dfs(int k,int a,int b){
	if(k>5){
		for(int i=1;i<=8;i++){
			for(int j=1;j<=8;j++)
				cout<<c[i][j];
			cout<<endl;
		}
		exit(0);
	}
	
	int g[15][15];
	for(int i=1;i<=8;i++)
		for(int j=1;j<=8;j++)
			g[i][j]=vis[i][j];
	
	for(int i=a;i<=8;i++){
		for(int j=(i==a?b:1);j<=8;j++){
			if(vis[i][j]) continue;
			
			c[i][j]='Q';
			for(int k=1;k<=8;k++){
				vis[i][k]=1;
				vis[k][j]=1;
			}

			for(int k=1;k<=8;k++){
				vis[i-k][j-k]=1;

				if(i-k==0||j-k==0)
					break;
			}

			for(int k=1;k<=8;k++){
				vis[i+k][j+k]=1;

				if(i+k==8||j+k==8)
					break;
			}

			for(int k=1;k<=8;k++){
				vis[i-k][j+k]=1;

				if(i-k==0||j+k==8)
					break;
			}

			for(int k=1;k<=8;k++){
				vis[i+k][j-k]=1;

				if(i+k==8||j-k==0)
					break;
			}
			dfs(k+1,i,j);
			
			c[i][j]='.';
			for(int o=1;o<=8;o++)
				for(int p=1;p<=8;p++)
					vis[o][p]=g[o][p];
		}
	}
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	for(int i=1;i<=8;i++)
		for(int j=1;j<=8;j++)
			cin>>c[i][j];

	for(int i=1;i<=8;i++){
		for(int j=1;j<=8;j++){
			if(c[i][j]=='Q'){
				for(int k=1;k<=8;k++){
					if((c[i][k]=='Q'&&k!=j)||(c[k][j]=='Q'&&k!=i)){
						cout<<"No Answer"<<endl;
						return 0;
					}
					
					vis[i][k]=1;
					vis[k][j]=1;
				}
				
				for(int k=1;k<=8;k++){
					if(c[i-k][j-k]=='Q'){
						cout<<"No Answer"<<endl;
						return 0;
					}
					
					vis[i-k][j-k]=1;
					
					if(i-k==0||j-k==0)
						break;
				}

				for(int k=1;k<=8;k++){
					if(c[i+k][j+k]=='Q'){
						cout<<"No Answer"<<endl;
						return 0;
					}

					vis[i+k][j+k]=1;

					if(i+k==8||j+k==8)
						break;
				}
				
				for(int k=1;k<=8;k++){
					if(c[i-k][j+k]=='Q'){
						cout<<"No Answer"<<endl;
						return 0;
					}

					vis[i-k][j+k]=1;

					if(i-k==0||j+k==8)
						break;
				}
				
				for(int k=1;k<=8;k++){
					if(c[i+k][j-k]=='Q'){
						cout<<"No Answer"<<endl;
						return 0;
					}

					vis[i+k][j-k]=1;

					if(i+k==8||j-k==0)
						break;
				}
			}
		}
	}
	
	dfs(1,1,1);
	
	cout<<"No Answer"<<endl;
	
	return 0;
}

レースゲーム

题目传送门

レースゲーム

题目大意

给定起点和终点,给出 n 个向量,构成一条路径,求在这个路径上从起点到终点的最短路,考虑小数精度。

1N2×105

1li,ri106

思路

咕咕咕。

代码

咕咕咕。

贪心只能过样例

题目传送门

贪心只能过样例

题目大意

有一个序列 {an}ai 有一个取值范围 [li,ri],求 ai2 有几种可能。

1n,li,ri100

思路

题目都提示过了,贪心只能过样例,所以这道题用 DP。

fi,j=[0,1] 为前 i 个元素是否可以等于 j,不难发现:

k[li,ri],fi,j=max{fi1,jk2}

k[0,106],ans=fn,k

是这样的,但是复杂度估计得爆炸。

注意到 fi,j 的取值只有 0,1,那么对于这个东西取 max 等价于取或。

bitset 优化该解法。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n;
bitset<1000001>f[101];

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n;
	
	f[0][0]=1;
	for(int i=1;i<=n;i++){
		int l,r; cin>>l>>r;
		
		for(int j=l;j<=r;j++){
			f[i]|=(f[i-1]<<(j*j));
		}
	}
	
	cout<<f[n].count()<<endl;
	
	return 0;
}

[eJOI2020 Day1] Fountain

题目传送门

[eJOI2020 Day1] Fountain

题目大意

咕咕咕。

思路

神仙题,把各大算法非常好地联系在了一起。

我们发现每一次往下流,只会流到第 i 个喷泉的下面的第一个直径大于 i 的喷泉,这显然是单调栈的应用。

但是这样一个一个跟着单调栈枚举下去最坏情况的复杂度为 O(n2),显然过不了,考虑优化枚举过程。

发现对于 i,j(i<j) 来说,枚举到 j 的时候只影响水的体积,而不影响其他的变量,所以我们可以考虑倍增。

不难想到,对这个问题倍增肯定要预处理出所有 toi,j 表示从 i 往后走 2j 个的节点和从 i2j 中区间喷泉容积总和(用于维护加水量体积的改变)。然后题就写完了,剩下的都是倍增的典型写法。

时间复杂度 O(nlogn)

(以下是闲话)

AC 完这道题之后可以去看看洛谷的题解,里面有一个是把这个题转化成树上倍增 LCA 的算法非常巧妙,建议去看看。

这是欧洲 OI 的题,洛谷上这道题评分为 绿,但其实难度不小,我觉得难度应该有

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n,q;
int d[100005],c[100005];
stack<int>s;
int to[100005][55]={0};
int sum[100005][55]={0};

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);

	cin>>n>>q;

	for(int i=1;i<=n;i++)
		cin>>d[i]>>c[i];

	d[++n]=1e17; c[n]=1e17;

	for(int i=1;i<=n;i++){
		while(!s.empty()&&d[i]>d[s.top()]){
			to[s.top()][0]=i;
			sum[s.top()][0]=c[i];
			s.pop();
		}

		s.push(i);
	}
	
	for(int j=1;j<=19;j++){
		for(int i=1;i+(1<<j)-1<=n;i++){
			to[i][j]=to[to[i][j-1]][j-1];
			sum[i][j]=sum[i][j-1]+sum[to[i][j-1]][j-1];
		}
	}
	
	while(q--){
		int r,v; cin>>r>>v;
		
		if(v<=c[r]){
			cout<<r<<endl;
			continue;
		}
		
		v-=c[r];
		
		for(int i=19;i>=0;i--){
			if(to[r][i]!=0&&v>sum[r][i]){
				v-=sum[r][i];
				r=to[r][i];
			}
		}
		
		cout<<(to[r][0]==n?0:to[r][0])<<endl;
	}

	return 0;
}

最大食物链计数

题目传送门

最大食物链计数

题目大意

给定一个有向无环图 G,点集为 V,求 uV,vV[path(u,v)]×[in(u)=0]×[out(v)=0]

也就是求图中一对 u,v 的个数,满足以下条件:

  • uv 之间存在一条路径。

  • u 入度为 0

  • v 出度为 0

n5000

思路

很水的套板子题。求路径计数,很显然地使用 DP。

fi 表示从任意一个入度为 0 的点到达 i 的所有方案个数。很显然,我们建立一个反图 G,那么有:

fi=uG,path'(i,u)fu

显然为了满足无后效性,我们需要把图拓扑排序。

这道题确实水了一些,但是可以作为图上 DP 的入门题,也是一道好题。

最坏时间复杂度为 O(n2)

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n,m,in[100005]={0},out[100005]={0};
vector<int>V[100005];
vector<int>U[100005];
int f[100005]={0},tp[100005]={0},cnt=0;
const int MOD=80112002;
queue<int>q; int ans=0;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);

	cin>>n>>m;

	for(int i=1;i<=m;i++){
		int u,v; cin>>u>>v;
		V[u].push_back(v);
		U[v].push_back(u);
		in[v]++; out[u]++;
	}
	
	for(int i=1;i<=n;i++){
		if(!in[i]){
			q.push(i);
			f[i]=1;
		}
	}
	
	while(!q.empty()){
		int t=q.front();
		q.pop(); tp[++cnt]=t;
		
		for(int i=0;i<V[t].size();i++){
			if(in[V[t][i]]){
				in[V[t][i]]--;
				
				if(!in[V[t][i]])
					q.push(V[t][i]);
			}
		}
	}
	
	for(int i=1;i<=n;i++){
		int p=tp[i];
		
		for(int j=0;j<U[p].size();j++){
			f[p]+=f[U[p][j]];
			f[p]%=MOD;
		}
	}
	
	for(int i=1;i<=n;i++){
		if(out[i]==0){
			ans=ans+f[i];
			ans%=MOD;
		}
	}
	
	cout<<ans<<endl;

	return 0;
}

[USACO23JAN] Moo Route S

题目传送门

[USACO23JAN] Moo Route S

题目大意

有一个点在数轴上从原点移动了若干次后回到了原点。给出其经过的 ii+1 的次数,构造其的移动方案,使得变向次数最小。

Ai106

思路

显然我们应该设一个标记方向的变量 d 判断到当前点的转移方向。注意到需要让其转变的方向最小,所以我们最开始一直向右走,然后到头了再向左走。以此往复。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n,a[100005]={0};

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	cin>>n;
	
	for(int i=1;i<=n;i++)
		cin>>a[i-1];
		
	int pos=0,d=1,r=n+1;
	
	while(1){
		if(pos==0&&a[pos]==0){
			break;
		}
		
		if(d==1){
			if(a[pos]>0){
				a[pos]--;
				cout<<"R";
				pos++;
			}
			else{
				a[pos-1]--;
				cout<<"L";
				pos--; d=-1;
			}
		}
		else{
			if(a[pos-1]>1||a[pos]==0){
				a[pos-1]--;
				cout<<"L";
				pos--;
			}
			else{
				a[pos]--;
				cout<<"R";
				pos++; d=1;
			}
		}
	}
	
	return 0;
}

[ABC251D] At Most 3 (Contestant ver.)

题目传送门

[ABC251D] At Most 3 (Contestant ver.)

题目大意

给出 w,要求构造一个长为 n 的序列,要求:

  • n300

  • 对于每一个 1kw,都有在 {An} 中取出最多三个数的和等于 k

1w106

思路

显然,这道题和 w 是没有关系的。

考虑将一个 1000000 以内的数拆位,也就是将这个数看成 abcdef,然后预处理出所有的 abcdef 输出就行了。

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	
	int w; cin>>w;
	cout<<99*3<<endl;
	
	for(int i=1;i<=99;i++){
		cout<<i*10000<<' ';
	}
	
	for(int i=1;i<=99;i++){
		cout<<i*100<<' ';
	}
	
	for(int i=1;i<=99;i++){
		cout<<i<<' ';
	}

	return 0;
}
posted @   SnapYust  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示