C++题集


# DP问题

# Dice Sum

题目传送门:Atcoder Beginner Contest 248 C Dice Sum (opens new window)
反思:做的时候想到了可能会是DP,但是并没有深入的去想该怎么表示状态,状态转移方程是什么。
思路:
状态表示:dp[i][j]表示前i个数,和为j。
状态转移方程:dp[i][j] = dp[i][j] + (dp[i-1][0]+dp[i][1]+....)

AC代码
#define int long long
using namespace std;
const int mod = 998244353;
const int N = 60,M=2510;
int dp[N][M];
signed main()
{
  int t=1;
  while(t--){
    int n,m,k;cin>>n>>m>>k;
    dp[0][0]=1;
    for(int i=1;i<=n;i++){
      for(int j=1;j<=k;j++){
        for(int u=1;u<=m;u++){
          if(j-u>=0){
            dp[i][j] = (dp[i][j] + dp[i-1][j-u]) % mod;
          }
        }
      }
    }
    int ans = 0;
    for(int i=1;i<=k;i++) ans = ( ans + dp[n][i] ) % mod;
    cout<<ans<<endl;
  }
  return 0;
}

# King Bombee

题目传送门:Atcoder Beginner Contest 244 E King Bombee (opens new window)
反思:赛时做的时候就以为纯纯图论,但是自己还是不太熟悉,导致完全没有想到是DP。
题解:
先来思考几个问题:
1.为什么是DP?
ans:取模,奇偶,求方案数
2.怎么表示状态?
ans:用dp[i][j][k],三维数组来表示,dp[i][j][k]表示走到值为i,第j个位置时的数量,k则用0/1表示X出现了奇数次还是偶数次。
3.X出现奇数次和偶数次的状态如何表示?

//如果说这个值和X相等
dp[i][j+1][1/0] += dp[i][j][0/1];
//如果不等呢?
dp[i][j+1][1/0] += dp[i][j][1/0];

AC代码:

点击查看代码
//Ryker
#define int long long
using namespace std;
const int mod = 998244353;
int dp[2010][2010][2];
pair<int, int> v[2010];
signed main() {
	int N,M,K,S,T,X;
//	数列里面要有k+1个点,全部从1-n里面拿
//	要满足起点是s,终点是t,并且A[i]要和A[i+1]相连
//	x出现偶数次
	
//	思路:dp[i][j][k]表示第i个点,序列中第j个位置,x出现的奇数还是偶数次
	cin>>N>>M>>K>>S>>T>>X;
	for(int i=1;i<=M;++i){cin>>v[i].first;cin>>v[i].second;}
	dp[S][0][0]=1;
	for(int i=0;i<K;++i){
		for(int j=1;j<=M;++j){
			int x = v[j].first;int y=v[j].second;
			for(int k=0;k<=1;++k){
//				奇数和偶数的转换
				(dp[x][i+1][k] += dp[y][i][k ^ (X==y)]) %= mod;
				(dp[y][i+1][k] += dp[x][i][k ^ (X==x)]) %= mod;
			}
		}
	}
	cout<<dp[T][K][0]<<endl;
//	x x x z
	return 0;
}

# Choose Elements

题目传送门:Atcoder Beginner Contest 245 C Choose Elements (opens new window)
题意:
给两个数组,A[N],B[N],然后对每个数(i),你可以选择A[i]或者B[i],构成一个数组X[N],要求这个数组满足(X[i]-X[i+1])的绝对值小于K

过程:在赛场上始终想的是必须处理到终点,但我并不会处理(,然后想着DFS必然会TLE,一直在这里犹疑不决。

题解:其实这题可以用DP,首先状态表示是DP[i][2],前i表示走了第i个,第二个维度0表示选A,1表示选B,状态转移的时候注意:如果你前面没走,你后面自然也不能走。
AC代码:

点击查看代码
//Ryker
#define int long long
using namespace std;
const int N = 2e5+10;
int a[N];
int b[N];
int dp[N][2];
int n,k,cnt=0;
signed main() {
	int t=1;
	while(t--){
		cin>>n>>k;
		for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
		for(int i=1;i<=n;i++) scanf("%lld",&b[i]);
		dp[1][1]=1;dp[1][0]=1;
		for(int i=1;i<n;i++){
			if(abs(a[i]-a[i+1])<=k) if(dp[i][0]) dp[i+1][0] = 1;
			if(abs(a[i]-b[i+1])<=k) if(dp[i][0]) dp[i+1][1] = 1;
			if(abs(b[i]-a[i+1])<=k) if(dp[i][1]) dp[i+1][0] = 1;
			if(abs(b[i]-b[i+1])<=k) if(dp[i][1]) dp[i+1][1] = 1;
		}
		for(int i=1;i<=n;i++){
			if(!dp[i][0]&&!dp[i][1]) break;
			else cnt++;
		}
		if(cnt==n) cout<<"Yes"<<endl;
		else cout<<"No"<<endl;
	}
	return 0;
}

# 搜索问题

# Shortest Good Path(BFS)

题目传送门:Atcoder Beginner Contest 244 F Shortest Good Path (opens new window)

题意:
给你n个点,m条边,无重边,无自环
一个数列如果满足,所有的点都在1-N之间,并且两点之间有边连就可以
还有1/0表示路径经过i点的次数是奇数还是偶数
题解:
1.如何表示?
(1)输入时用一个vector表示,后续用auto得到对应的值。
(2)dis[i][j]中i表示现在的状态「用二进制来表示」,j表示是啥数。
2.如何做?
(1)为什么一开始要u-1,v-1?因为要使用二进制的操作,比如0001,是(1<<0)而不是(1<<1)
(2)BFS,从一个数开始往后搜索,如果没搜过,就继续搜,然后+1,如果搜过了,那就不要了
AC代码:

点击查看代码
//Ryker
#define int long long
using namespace std;
const int N = (1 << 17);
const int INF = 0x3f3f3f3f3f3f3f3f;
int dis[N][17];
vector<int> G[20];
queue<pair<int, int>> Q;
int n,m;
signed main() {
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int u,v;
		cin>>u>>v;
		u--,v--;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	int M = 1<<n;
	for(int i=0;i<M;i++)
		for(int j=0;j<n;j++)
			dis[i][j]=INF;
//		这里为啥要一开始初始化为1呢?
//		因为我一个数,length为1!
	for(int i=0;i<n;i++){
		dis[1<<i][i]=1;
		Q.push({1<<i,i});
	}
	//		BFS
	while(Q.size()){
		int s = Q.front().first;
		int v = Q.front().second;
		Q.pop();
		for(auto u : G[v]){
			int ns = s ^ (1<<u);
			if(dis[ns][u]<INF) continue;
			dis[ns][u] = dis[s][v] + 1;
//			这就是挑出来的那一个
			Q.push({ns,u});
		}
	}
	int ans = 0;
//	最后是对所有情况进行加总
	for(int i=1;i<M;i++){
		int mm = INF;
		for(int j=0;j<n;j++){
			mm = min(mm,dis[i][j]);
		}
		ans += mm;
	}
	cout<<ans<<endl;
}

# 思维题

# Bracket Sequence Deletion

题目传送门:CodeForces edu 125 C (opens new window)
题意:
1.如果括号可以配对,就可以删去
2.如果括号回文,就可以删去
3.找最短的可以删去的括号对
4.满足两端括号相等时,这一个括号组回文
题解:
1.'))','((','()'这种既是最短,又满足条件,可以直接删去
2.只需要讨论')('这种情况,往后找到第一个')'就可以了。
AC代码:

点击查看代码
//Ryker
#define int long long
using namespace std;
const int N = 5e5+10;
char str[N];
signed main() {
	int t;cin>>t;
	while(t--){
		int n;cin>>n;
//		一开始为0;
		int ans2=0,ans1=0;
		scanf("%s",str+1);
		for(int i=1;i<=n;i++){
			if(str[i] == ')' && str[i+1] == '('){
				int j = i + 1;
				while(j<=n && str[j]!=')') j++;
				if(str[j]==')') ans1++,i=j;
				else {
					ans2 = n-i+1;
					break;
				}
			}
			else if (i != n) {
				ans1++;
				i ++;
			}
			else ans2 = 1;
		}
		cout<<ans1<<" "<<ans2<<endl;
	}
	return 0;
}

# Max Min

题目传送门:Atcoder ABC 247 E (opens new window)
过程:赛时做的时候想的是滑动窗口,但做不出来(
赛后群佬教给了我一种方法==>容斥原理!
首先构造一个函数,这个函数表示区间内的范围的数量。
比如:

//求x到y这个范围中,最大值小于等于x,最小值大于等于y的范围的个数
int getRangeOf(int x,int y)//x最大值,y最小值 
{
	int res = 0;
	for(int i = 1,j = 1;i <= N; ++ i)
	{
		if(a[i] > x || a[i] < y)j = i + 1;
		else res += i - j + 1;
	}
	return res;
}

这样操作之后
再用一个容斥原理(类似于二维前缀和的方式写就可以了!

细节可以看看代码
#include <iostream>
#include <string.h>
//#include <__clang_cuda_cmath.h>
#include <algorithm>
#include <queue>
#define int long long
using namespace std;
const int N1 = 2e5+10;
int N;
int X,Y;
int A[N1];
int rang(int x,int y){
	int res = 0;
	for(int i=1,j=1;i<=N;i++){
		if( A[i] > x || A[i] < y ) j = i + 1;
		else res += i - j + 1;
	}
	return res;
}
signed main(){
	cin>>N>>X>>Y;
	for(int i=1;i<=N;i++) cin>>A[i];
	// Y + 1 因为是底下!
	int ans = rang(X,Y) - rang(X-1,Y) - rang(X,Y+1) + rang(X-1,Y+1);
	printf("%lld\n",ans);
	return 0;
}

# Math

# K-colinear Line

题目传送门:Atcoder 248 E K-colinear Line (opens new window)
题解:
1.一条线就不用考虑了「需要一条线意味着k==1」
2.两个点组成一条线,最多有n*(n-1)/2条线
3.每次挑没走过的,然后判断在不在一条线上可以用向量
「x1,y1」「x2,y2」「x3,y3」 ( y2 - y1 ) / ( x2 - x1 ) = ( y3 - y1 ) / ( x3 - x1 )
这里用的是斜率
得到( y2 - y1 ) * ( x3 - x1 ) = ( y3 - y1 ) * ( x2 - x1 )

AC代码
#define int long long
using namespace std;
const int mod = 998244353;
//const int N = 60,M=2510;
const int N = 310;
int X[N],Y[N];
int f[N][N];
//判断在不在一条直线上
bool strddd(int ze,int on,int tw){
  int a1 = ( X[on] - X[ze] ) * ( Y[tw] - Y[ze] );
  int a2 = ( X[tw] - X[ze] ) * ( Y[on] - Y[ze] );
  return (a1==a2);
}

signed main()
{
  int n,k;cin>>n>>k;
  for(int i=0;i<n;i++) cin>>X[i]>>Y[i];
  if(k==1) {
    printf("Infinity");
    return 0;
  }
  vector<int> list;
  int ans = 0;
  //最多有多少种线
  for(int i=0;i<n;i++)
    for(int j=i+1;j<n;j++)
      f[i][j]=true;
  //开始枚举了
  for(int i=0;i<n;i++){
    for(int j=i+1;j<n;j++){
      if(f[i][j]){
        int cnt = 2;
        list.clear();
        list.push_back(i);
        list.push_back(j);
        for(int ii=j+1;ii<n;ii++){
          if(strddd(i,j,ii)){
            cnt++;
            list.push_back(ii);
          }
        }
        //枚举完记得清理
        for(int ii=0;ii<cnt;ii++)
          for(int jj=ii+1;jj<cnt;jj++)
            f[list[ii]][list[jj]] = false;
        if(cnt >= k) ans++;
      }
    }
  }
  cout<<ans<<endl;
  return 0;
}

# Polynomial division

题目传送门:Atcoder 245 D (opens new window)
过程:
赛时看这个题:不就是大除法嘛,然后自己做傻眼了,没有想到要减去,要除,然后要找到有效减去位置。
题解:
对于每个不为0的首部,求出A的几倍是C「其实就是B」,把这个值加到B[N]中,然后用一个循环减去C[N]中的值,然后迭代着做。

See More
//Ryker 
#define int long long
const int N = 1e3+10;
int b[N],a[N],c[N];
int n,m;
void solve(){
	int k = n + m;
	//判断这个循环完了没有
	while(k>=0&&!c[k]) k--;
	if(k<0) return;
	//求出了这个前缀
	int ddd = c[k] / a[n];
	b[k-n] += ddd;
	for(int i=0;i<=n;i++) c[i+k-n] -= a[i]*ddd;
	solve();
}
signed main() {
	int t=1;
	while(t--){
		cin>>n>>m;
		for(int i=0;i<=n;i++) cin>>a[i];
		for(int i=0;i<=n+m;i++) cin>>c[i];
		solve();
		for(int i=0;i<=m;i++) cout<<b[i]<<' ';
		cout<<endl;
	}
	return 0;
}

# 贪心问题

# Wrapping Chocolate

题目传送门:Atcoder 245 E (opens new window)
过程:把巧克力放到大小匹配的盒子里,贪心嘛,就是它有两个维度,你怎么贪?
思路:
主要是你如何存,这里用到了vector套pair和multiset的数据结构,sort就很好sort了,先比较vector的第一个维度,然后再比较vector的第二个维度,最后第三个,如果都一样,我盒子的第三个值是1,同样放到前面。
后面的操作就是如果盒子在前面,就放到multiset的数据结构里,如果是巧克力,就进去找有没有合适的盒子。
注意:它这里是用的第二个维度来找,也就是长度,本身宽度是符合要求才会进入multiset的结构中的。

See More
#define int long long
using namespace std;
const int N = 2e5+10;
int n,m;
int a[N],b[N],c[N],d[N];
//难点是选择构造这两个数据结构
vector<array<int,3>> q;
//实现了一个有序数组
multiset<int> s;
signed main() {
	cin>>n>>m;
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++) scanf("%lld",&b[i]);
	for(int i=1;i<=m;i++) scanf("%lld",&c[i]);
	for(int i=1;i<=m;i++) scanf("%lld",&d[i]);
	for(int i=1;i<=n;i++) q.push_back({a[i],b[i],0});
	for(int i=1;i<=m;i++) q.push_back({c[i],d[i],1});
	sort(q.begin(),q.end());
	reverse(q.begin(),q.end());
	for(auto x : q ){
		if(x[2]==1) s.insert(x[1]);
		else {
			auto dd = s.lower_bound(x[1]);
			if(dd==s.end()){
				printf("No\n");
				return 0;
			}
			s.erase(dd);
		}
	}
	printf("Yes\n");
}

# 二分问题

# Range Count Query

题目传送门:Atcoder 248 D Range Count Query (opens new window)
反思:
赛时做的时候只想正常的二分"值",没有想到可以二分L和R,开一个二维数组用空间换时间
题解:
开一个二维数组,将一样大的值放到同一个维度里,存入这个值的位置,然后二分搜索位置即可。

AC代码
using namespace std;
const int mod = 998244353;
const int N = 2e5+10;
int A[N];
int B[N];
signed main()
{
  int n;cin>>n;
  vector<vector<int>> idx(n+1);
  for(int i=1;i<=n;i++){
    int num;cin>>num;
    idx[num].push_back(i);
  }
  int Q;cin>>Q;
  while(Q--){
    int L,R,X;cin>>L>>R>>X;
    cout<<upper_bound(idx[X].begin(),idx[X].end(), R) - upper_bound( idx[X].begin(),idx[X].end(), L-1)<<endl;
  }
  return 0;
}

# 木材加工

题目传送门:洛谷二分题单 木材加工 (opens new window)
反思:
做的时候疯狂写假,始终有一个点会有浮点数错误,唔,还没搞清楚原因(ps:有时这种写法是对的)

while(l<=r){
	int mid = ( l + r ) / 2;
	if(func(mid)>k) r = mid + 1
	else r = mid - 1
}

(有时这种写法是对的,要命哩)

while(l+1<r){
	int mid = ( l + r ) / 2;
	if(func(mid)>k) r = mid;
	else r = mid;
}

::: AC代码

#define int long long
using namespace std;
const int N = 1e6+10;
int n,k;
int A[N];

int func(int ch){
  int ans = 0;
  for(int i=1;i<=n;i++){
    ans += A[i] / ch;
  }
  return ans;
}

signed main()
{
  cin>>n>>k;
  int sum = 0;
  for(int i=1;i<=n;i++) {cin>>A[i];sum+=A[i];}
  // 直接做除法看能分几段咯
  sort(A+1,A+1+n);
  int l = 0, r = A[n];
  // 这里来个边界条件
  while(l + 1<r){
    int mid = ( l + r ) / 2;
    //这里来个具体的,直接得到mid
    //建议使用 l + 1 < r 这样来写
    if(func(mid)>=k) l = mid;
    else r = mid;
  }
  cout<<l<<endl;
  return 0;
}

:::

# 跳石头

题目传送门:洛谷二分题单 P2678 跳石头 (opens new window)
过程:
这道题之前做过一遍了(,现在做还是懵逼的状态,主要是完全不知道怎么二分。
思路:

从答案入手!!!

通过答案来二分,主要是二分函数那一部分的写法,是以一个跳石头的人的思路来看的,
如果说两个石头的之间的距离小于最小距离,那这块石头就要被我丢掉,如果大于捏,直接跳!

AC代码
#define int long long
using namespace std;
const int N = 1e6+10;
int L,n,m;
int A[N];
bool cal(int mid){
  int cnt=0,now=0,i=0;
  while(i<n+1){
    i++;
    if(A[i]-A[now]<mid) cnt++;
    else now = i;
  }
  if(cnt>m) return false;
  return true;
}
signed main()
{
  // l -> 起点到终点的距离
  // n -> 起点和终点之间的岩石数
  // m -> 以及组委会至多移走的岩石数
  cin>>L>>n>>m;
  for(int i=1;i<=n;i++) cin>>A[i];
  int l = 1,r = L;
  A[0] = 0,A[n+1] = L;
  int ans = L;
  while(l+1<r){
    int mid = (l+r)/2;
    if(cal(mid)){
      l = mid;
      ans = mid;
    }else r = mid;
  }
  cout<<ans<<endl;
  return 0;
}