1.20~1.26

codeforces 991 div.3

A

题目

字符串处理,题意大概是:给你一个数字,问你有多少个完整的单词的字母数之和不超过这个数字。很简单的字符串题,运用字符串的size()函数即可。

代码

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

int main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int t;
	cin >> t;
	while(t--){
		int n,m,sum=0;
		string s;
		cin >> n >> m;
		while(n--){
			cin >> s;
			int len = s.size();
			m-=len;
			if(m>=0){
				sum++;
			}
		}
		cout << sum << '\n';
	}
	return 0;
}

B

题目

给你一个数组,让你在数组中出去第一个和最后一个数,剩下的数中选则一个数,并做以下操作之一:

  1. 该数的前一个数减一,后一个数加一
  2. 该数的前一个数加一,后一个数减一

问你在做n次操作后,能不能使得这个数组中所有数都相同

思考后发现,这些操作是有规律的,所有的操作都可以分为这两类:

  1. 奇数项进行数字加减
  2. 偶数项进行数字加减

我们只要判断:

  1. 所有数的和是否能整除n (因为数组最后全为n)
  2. 奇数项之和能否整除奇数的项数
  3. 偶数项之和能否整除偶数的项数

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 2e5;

int a[maxn+1];

signed main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int t;
	cin >> t;
	while(t--){
		memset(a,0,maxn+1);
		int n,n_ji=0,sum=0,sum_ji=0,sum_ou=0;
		cin >> n;
		for(int i=0;i<n;i++){
			cin >> a[i];
			sum+=a[i];
			if((i+1)%2){
				sum_ji +=a[i];
				n_ji++;
			}
			else sum_ou +=a[i];
		}
		if(sum%n){
			cout << "NO\n";
			continue;
		}
		int ave = sum/n;
		if(sum_ji/n_ji != ave || sum_ou/(n-n_ji) != ave)
			cout << "NO\n";
		else cout << "YES\n";
	}
	return 0;
}

C

题目

题目大意:给你一个位数不超过1e5的数,你可以把里面的任意个1,2,3进行平方处理,问你是否可以出 现处理之后的数能被9整除?

数的位数可以长达1e5,就需要用字符串来处理;小学老师讲过,能被9整除的数,该数的每一位数字之和可以被9整除。所以用sum来记录每位数之和,er记录2出现的次数,san记录3出现的次数。如果sum不能被9整除,那么就跑暴力:判断sum+er_i2+san_i6是否能被9整除

代码

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

void add_er_san(int sum,int er,int san){
	for(int i=0;i<=er;i++){
		for(int j=0;j<=san;j++){
			if((sum+i*2+j*6)%9==0){
				//cout<<"sum:"<<sum<<"  er:"<<i<<"  san:"<<j<<'\n';
				cout << "YES\n";
				return;
			}
		}
	}
	//cout<<"sum:"<<sum<<"  er:"<<er<<"  san:"<<san<<'\n';
	cout << "NO\n";
	return;
}
int main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int t;
	cin >> t;
	while(t--){
		string n;int sum=0,er=0,san=0;
		cin >> n;
		int len = n.size();
		for(int i=0;i<len;i++){
			int ni = n[i] - '0';
			sum += ni;
			if(ni == 2) er++;
			else if(ni == 3) san++; 
		}
		if(sum%9 == 0){
			cout << "YES\n";
			continue;
		}
		add_er_san(sum,er,san);
	}
	return 0;
}

D

题目

给你一个由0~9组成测字符串s,你可以把除了最左边的和0以外的数做这种操作:

该数-1,然后和它左边的数对调

问你任意次操作后能得到的词序最大的字符串是什么

显然,想让数尽可能大,就要尽可能让大的数排在左边。每一位数最大为9,最多移动9格,所以对于每一位数,考虑它右边9格之内的数,哪个数移到该格最大就移动哪个数

代码

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

int main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int t;
	cin >> t;
	while(t--){
		string n;
		cin >> n;
		int len = n.size();
		if(len == 1){
			cout << n[0] << '\n';
			continue;
		}
		for(int i=0;i<len;i++){
			int t = n[i]-'0',index=0;
			for(int j=1;j<=9 && i+j<len;j++){
				if(n[i+j]-'0'-j > t){
					t=n[i+j]-'0'-j;
					index=i+j;
				}
			}
			if(index == i) continue;
			for(int j=index;j>i;j--){
				swap(n[j],n[j-1]);
			}
			n[i]=t+'0';
		}
		for(int i=0;i<len;i++)
			cout << n[i];
		cout << '\n';
	}
	return 0;
}

E

题目

题目大意:输入字符串a,b,字符串c由a,b融合来组成(ab融合时不能改变每个字符原本的前后顺序,如字符串a是er,字符串b是f,则字符串c不能为ref,可以为erf,efr,fer)

再输入一个可能被更改过的字符串c,问你a和b融合后至少要更改几个字符才能变成给出的字符串c

思路:dp,用dp[i][j]来表示c的前i+j个字符需要修改的字符数,a对应i,b对应j

一般状态转移方程为

dp[i][j] = min(dp[i-1][j] + (a[i-1]!=c[i+j-1]), dp[i][j-1] + (b[j-1]!=c[i+j-1]))

有i=0和j=0的特判,这俩表示只用b和只用a来组成c

代码

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

int dp[1001][1001];

int main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int t;
	cin >> t;
	while(t--){
		string a,b,c;
		for(int i=0;i<1001;i++)
			for(int j=0;j<1001;j++)
				dp[i][j]=0;
		cin >> a >> b >> c;
		int len_a=a.size(),len_b=b.size();
		for(int i=0;i<=len_a;i++){
			for(int j=0;j<=len_b;j++){
				if(i==0 && j==0) continue;
				else if(i==0) dp[i][j] = dp[i][j-1] + (b[j-1]!=c[j-1]);
				else if(j==0) dp[i][j] = dp[i-1][j] + (a[i-1]!=c[i-1]);
				else dp[i][j] = min(dp[i-1][j] + (a[i-1]!=c[i+j-1]), dp[i][j-1] + (b[j-1]!=c[i+j-1]));
			}
		}
		cout << dp[len_a][len_b] << '\n';
	}
	return 0;
}

牛客月赛109

A

题目

如题,这题可以不跑暴力,观察得到,如果n小于19375332,那么x=n;否则x=19375332

代码

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

signed main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int n;
	cin >> n;
	if(n<=19375332) cout << n;
	else cout << 19375332;
	return 0;
} 

B

题目

贪心

数组是自己切的,自己只能取数组的左边部分,右边部分给菲菲jj;想要使自己的平均数最大,那么可以只从自己的数组中取一个数——最大的那一个数,而欲使自己的数组中的最大的数尽可能大,就要让自己的数组中的数尽可能多,所以要把给的前n-1个数全部划分给自己,剩下的一个给菲菲jj;自己选出最大的平均数后,菲菲jj的唯一的数就是她的最大的中位数。最后判断就行

代码

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

vector <int> v1;

int main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int n,k1,k2,cache;
	cin >> n >> k1 >> k2;
	for(int i=0;i<n-1;i++){
		cin >> cache;
		v1.push_back(cache);
	}
	cin >> cache;
	sort(v1.begin(),v1.end(),greater<int>());
	if(v1[0]>cache) cout << "Yes\n";
	else cout << "No\n";
	
	return 0;
}

C

题目

题目如图

这道题按常规思路去做必定TLE,l到r一个一个去遍历的这种枚举做法 在最坏的情况下会使时间复杂度来到O(n2) , 带入数据得出1e10,所以必定TLE

可以用一个数组来优化。这个数组储存每次的查询入口,如果这段区间的猪儿还没有被主人玩过,那么这段区间的入口都设为r+1,表示如果下次访问到了这段区间,就跳到r+1之后再做访问。时间复杂度可以优化到O(nlogn)

这个优化方法和并查集非常像,但是比并查集简单很多

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5;
int van[maxn+1],jump[maxn+1];

int main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int n,q,op,l,r,x,index=0;
	cin >> n >> q;
	while(q--){
		cin >> op;
		if(op==1){
			cin >> l >> r;
			l--;r--;
			while(l<=r){
				if(!van[l]){
					van[l] = ++index;
					jump[l++] = r+1;
				}
				else l=jump[l];
			}
		}
		else{
			cin >> x;
			cout << van[x-1] << '\n';
		}
	}
	return 0;
}

D

题目

这道题我赛时没有做,下来学了背包后做的

sum表示一个猪儿都不陪,全部用钱搞定

s[i]表示第i只猪儿要从哪一天开始陪

day[i]表示调整值

day[s[i]+m-1]=min(day[s[i]+m-1],bi-vali)

dp[i]表示到第i天的最小费用,状态转移方程为:

dp[i]=min(dp[i-1],dp[i-m]+day[i])

代码

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

signed main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int n,m,sum=0;
	cin >> n >> m;
	vector <int> s(n+1),day(n+1,1e18),dp(n+1);
	for(int i=1;i<=n;i++) cin >> s[i];
	for(int i=1;i<=n;i++){
		int bi,vali;
		cin >> bi >> vali;
		sum+=vali;
		day[s[i]+m-1] = min(day[s[i]+m-1],bi-vali);
	}
	for(int i=m;i<=n;i++)
		dp[i] = min(dp[i-1],dp[i-m]+day[i]);
	cout << sum+dp[n] << '\n';
	return 0;
}

牛客周赛77

A

题目

纯签到

代码

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

int main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int t;
	cin >> t;
	if(t==1) cout << 20250121;
	else if(t==2) cout << 20250123;
	else if(t==3) cout << 20250126;
	else if(t==4) cout << 20250206;
	else if(t==5) cout << 20250208;
	else cout << 20250211;
	return 0;
}

B

题目

题目如图

这道题很像一层一层铺砖块,把这层铺完了才开始铺下一层

那么没有所有没有铺完的砖块层的层数差不会超过1

如果超过1了,就不行

代码

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

int main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int a[10]={0},t,cache;
	cin >> t;
	for(int i=0;i<t;i++){
		cin >> cache;
		a[cache]++;
	}
	int pd=0,maxn=-1,minn=99999999;
	for(int i=1;i<10;i++){
		maxn=max(maxn,a[i]);
		minn=min(minn,a[i]);
	}
	if(maxn-minn>1) pd=1;
	cout << (pd==0 ? "YES\n":"NO\n");
	return 0;
}

C

题目

如题,实质上是最大公约数的运用

对终点坐标x和y分开判断,x(或y)如果是cd(或ab)的最大公约数的倍数,那么就能走到

否则走不到

代码

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

int gcd(int a,int b){
	return (b==0 ? a : gcd(b,a%b));
}

signed main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int t;
	cin >> t;
	while(t--){
		int x,y,a,b,c,d;
		cin >> x >> y >> a >> b >> c >> d;
		if(x%gcd(c,d)==0 && y%gcd(a,b)==0) cout << "YES\n";
		else cout << "NO\n";
	}
	return 0;	
}

codeforces 998 div.3

A

题目

定义斐波那契数:ai+2=ai+ai+1

给你一个串数,你要在a1、a2、a4、a5四个数的中间插入a3,a3可以是任意整数

问你这串数中有多少个斐波那契数

思路:令给的数是a b c d,让x1=a+b,x2=c-b,x3=d-c

只需要判断:如果x1、x2、x3都相等,那么斐波那契数有3个;如果其中有两个相等,那么斐波那契有2个,否则1个

代码

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

int main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int t;
	cin >> t;
	while(t--){
		int a,b,c,d,x1,x2,x3;
		cin >> a >> b >> c >> d;
		x1=a+b,x2=c-b,x3=d-c;
		if(x1==x2 && x2==x3)
			cout << "3\n";
		else if((x1==x2 && x1!=x3) || (x1==x3 && x1!=x2) || (x2==x3 && x1!=x2))
			cout << "2\n";
		else cout << "1\n";
	}
	return 0;
}

B

题目

n头牛儿打牌,每个牛儿有m张牌,每个回合这n个牛儿都按顺序轮流出牌,要求第n头牛出的牌要比第n-1头出的大。

问你是否存在一种出牌顺序

思路:先用一个数组a记录从1到n*m对应的牛儿;由于每回合每个牛儿出牌顺序是固定的,我们用数组b记录第一回合的出牌顺序,也就是a的前n个。然后模拟每回合,判断b[i]是否等于a[i+j],只要出现不等于的情况就不行

代码

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

int main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int t;
	cin >> t;
	while(t--){
		int a[2001]={0},b[2001]={0},n,m,cache;
		cin >> n >> m;
		for(int i=1;i<=n;i++){
			for(int j=0;j<m;j++){
				cin >> cache;
				a[cache]=i;
			}
		}
		for(int i=0;i<n;i++) b[i]=a[i];
		int nm=n*m,pd=1;
		for(int i=0;i+n<=nm && pd;i+=n)
			for(int j=0;j<n;j++)
				if(b[j]!=a[i+j]){
					pd=0;
					break;
				}
		if(!pd) cout << -1;
		else for(int i=0;i<n;i++) cout << b[i] << ' ';
		cout << '\n';
	}
	return 0;
}

C

题目

给出n(偶数)个数,k,游戏进行n/2轮

每一轮依次进行以下操作:

Alice选择一个数a并从列表里删除它

Bob选择一个数b并从列表里删除它

如果a+b=k,分数+1

Alice的目标是使分数尽可能小,而Bob的目标是使分数尽可能大。假设双方都采用最优策略,问你游戏结束后分数是多少

思路:先把k拆成1+(k-1),2+(k-2)......这种成对的形式,然后判断列表里有多少个完整的对,这个成对的数目就是原始分数

Alice肯定会优先从大于等于k的数里面选择,因为没有另外一个正整数使得它们之和等于k,Alice选了大于等于k的数之后如果还有这种数,Bob也要选;如果没有大于等于k的数了,Bob就要从小于K的没有结成对的数里选;如果没有没有结成对的数,那么就让原始分数-1,意味着拆开一对数,拿其中一个去和Alice抵消

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 4e5;
int a[maxn+1];
int main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int t;
	cin >> t;
	while(t--){
		int n,k,cache,score=0,res=0,alice=0;
		memset(a,0,maxn+1);
		cin >> n >> k;
		for(int i=0;i<n;i++){
			cin >> cache;
			if(cache >= k) alice++;
			else a[cache]++;
		}
		for(int i=1;i<=k/2;i++){
			int j=k-i;
			if(i==j){
				score += a[i]/2;
				res += a[i]%2;
			}else{
				score += min(a[i],a[j]);
				res += abs(a[i]-a[j]);
			}
			//cout<<"i:"<<i<<" j:"<<j<<" score:"<<score<<" res:"<<res<<'\n';
		}
		if(res >= alice%2) cout << score << '\n';
		else{
			cout << score-1<< '\n';
		}
	}
	return 0;
}

D

题目

给出一串数,你可以做任意次以下操作:

从第1个到第n-1个数中选一个数ai,ai和ai+1都要减去min(ai,ai+1)

问你能否出现一个不递减的数列

思路:思考后发现,从1到n-1依次做这种操作,如果出现了ai不等于0,而ai+1等于0,那么就不行,否则可以。从局部到整体,贪心

代码

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

const int maxn = 2e5;
int a[maxn+1];

int main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int t;
	cin >> t;
	while(t--){
		int n,pd=1;
		memset(a,0,maxn+1);
		cin >> n;
		for(int i=0;i<n;i++)
			cin >> a[i];
		for(int i=0;i<n-1;i++){
			int minn = min(a[i],a[i+1]);
			a[i]-=minn,a[i+1]-=minn;
			if(a[i+1]==0 && a[i]!=0){
				pd=0;
				break;
			}
		}
		cout << (pd ? "YES\n":"NO\n");
	}
	return 0;
}

Algorithm--算法学习

倍增法

倍增就是"成倍增长",利用二进制本身倍增的特性,第i位的权值2i等于前面所有权值的和,即

2i** = 2i-1 + 2i-2 + ... + 21+20 +1**

一个整数n,它的二进制只有log2n位。如果要从0增长到n,可以以1,2,4,...,2k为跳板快速跳到n,这些跳板只有k=log2n

书上举了洛谷的一道题为例:P4155 国旗计划

主要用到化圆为线,贪心求最优区间,倍增优化

化圆为线,把原来的圆圈复制再相接以保持首尾关系

贪心,每个区间的左端点排序,在区间x后面的区间里选择不超过x区间右端点的最大左端点的区间

倍增,go[s][i]表示从第s个区间出发,走2i个最优区间后到达的区间

关键递推式: go[s][i] = go[go[s][i-1]][i-1]

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 4e5+1;

int n,m,n2;
int go[N][20];

struct soldier{
	int id,l,r;
	bool operator < (const soldier b) const{return l<b.l;}
}s[N*2];  //排序需要重载 <  

void init(){		//贪心+预计算倍增 
	int nxt=1;
	for(int i=1;i<=n2;i++){		//贪心求每个区间的下一个区间 
		while(nxt<n2 && s[nxt].l<=s[i].r)
			nxt++;
		go[i][0] = nxt-1;
	}
	for(int i=1;(1<<i)<=n;i++)	//倍增 
		for(int j=1;j<=n2;j++)	//每个区间后的第2^i个区间 
			go[j][i] = go[go[j][i-1]][i-1];
}

int res[N];
void answer(int x){		//从第x个开始 
	int len=s[x].l+m,cur=x,ans=1;
	for(int i=log2(N);i>=0;i--){
		int pos = go[cur][i];
		if(pos && s[pos].r<len){
			ans+=1<<i;
			cur=pos;
		}
	}
	res[s[x].id] = ans+1;
}

int main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin >> n >> m;
	for(int i=1;i<=n;i++){
		s[i].id=i;
		cin >> s[i].l >> s[i].r;
		if(s[i].r<s[i].l) s[i].r+=m;	//变环为链 
	}
	sort(s+1,s+n+1);
	n2=n;
	for(int i=1;i<=n;i++){				//拆环加倍为链 
		n2++;
		s[n2] = s[i];
		s[n2].l = s[i].l+m;
		s[n2].r = s[i].r+m;
	}
	init();
	for(int i=1;i<=n;i++) answer(i);
	for(int i=1;i<=n;i++) cout << res[i] << ' ';
	return 0;
}

01背包

(这只是正式开始学习背包,但是由于做题的原因,之前已经接触了一些背包问题、零零散散地学了一些了)

01背包问题通常为:给定n种物品和一个背包,第i个物品体积为ci,价值为wi,背包地容量为C。如何选择装入背包的物品,使装入背包中的物品的总价值最大?

以 HDU 2602 为例

题目

用自底向上的递推完成转移过程,dp[i][j]表示把前i个物品(从第1个到第i个)装入容量为j的背包中获得的最大价值。假设现在递推到dp[i][j],分两种情况:

(1) 第i个物品的体积比j还大,则不能装入当前背包,直接继承前i-1个物品装入容量为j的背包,即dp[i][j] = dp[i-1][j]

(2) 第i个物品体积比j小,能装进背包,又分为两种:装或不装

① 装。 dp[i][j] = dp[i-1][j-c[i]] + w[i]

② 不装。 dp[i][j] = dp[i-1][j]

取①②中的最大值,状态转移方程为

dp[i][j] = max(dp[i-1][j-c[i]]+w[i],dp[i-1][j])

代码

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

const int N = 1001;
int w[N],c[N],dp[N][N];

int solve(int n,int C){
	for(int i=1;i<=n;i++)		//第i个物品 
		for(int j=0;j<=C;j++){	//容量为j 
			if(c[i] > j) dp[i][j] = dp[i-1][j]; 	//大于背包容量直接继承 
			else dp[i][j] = max(dp[i-1][j],dp[i-1][j-c[i]]+w[i]);
		}
	return dp[n][C];
}
int main(void){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int t;
	cin >> t;
	while(t--){
		int n,C;
		cin >> n >> C;
		for(int i=1;i<=n;i++)
			cin >> w[i];
		for(int i=1;i<=n;i++)
			cin >> c[i];
		memset(dp,0,sizeof(dp));
		cout << solve(n,C) << '\n';
	}
	return 0;
}
posted @ 2025-06-14 12:20  HLAIA  阅读(29)  评论(0)    收藏  举报