2021.10.7 2020 CCPC重现赛

传送门

1.最大权值排列

考察:思维题

思路:
要使权值和最大,分析题目以及样例可知,先将奇数从小到大排,再将偶数从大到小排。

代码如下:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5+10;
int a[N],v[N];

int main(){
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		if(i&1) cout<<i<<" ",v[i]=1;
	}
	for(int i=n;i>=1;i--){
		if(!v[i]) cout<<i<<" ";
	} 
	return 0;
}

2.合并!

考察:思维题

思路
其实你会发现不管怎么合并,答案都是一样的,所以直接从头到尾依次合并即可。

代码如下

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5+10;
int a[N],v[N];

int main(){
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		if(i&1) cout<<i<<" ",v[i]=1;
	}
	for(int i=n;i>=1;i--){
		if(!v[i]) cout<<i<<" ";
	} 
	return 0;
}

3.图与三角形

考察:图

思路
正难则反,要求同色三角形的个数,那么用总的三角形个数减去不满足条件的
三角形个数即可。
总三角形个数为:C(n,3)= n*(n-1)*(n-2)/6
不满足条件的三角形:
一定会有两条边颜色不同,而这条边是由某个点延伸出去的。

那么枚举点,再枚举它的边,res+=v[i].size()*(n-v[i].size()-1);
。由于里面有重复的,所以最后 res/2;

所以最后答案为:n*(n-1)*(n-2)/6-res/2

代码如下:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5010;
vector<ll> v[N];
ll n,a,b,c,p,d,res;
void rd(long long &x){
    char c;
    for (x=0,c=getchar();c<48;c=getchar());
    for (;c>47;c=getchar()) x=(x<<1)+(x<<3)+(c^48);
}
int main(){
	rd(n);
	rd(a),rd(b),rd(c),rd(p),rd(d);
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			if((a*(i+j)*(i+j)+b*(i-j)*(i-j)+c)%p>d){
				v[i].push_back(j);
				v[j].push_back(i);
			}
		}
	}
	for(int i=1;i<=n;i++){
		res+=v[i].size()*(n-v[i].size()-1);
	}
	cout<<1ll*n*(n-1)*(n-2)/6-res/2<<endl;
	return 0;
}

4.酒馆战棋

考察:模拟

思路
分开求最大和最小值。
求最大值时,因为如果有带嘲讽的,要优先攻击带嘲讽的。
(1)当没有剧毒时,没错,就是拿来当炮灰的,那么就是用来将那些有圣盾的去掉的,
所以先考虑圣盾嘲讽,再考虑一般嘲讽,再考虑圣盾。
(2)当有剧毒时,直接上啊!先kill一般嘲讽的,再去考虑圣盾嘲讽。
其次考虑普通的,最后考虑圣盾。

求最小值时,那么尽量先攻击圣盾嘲讽,尽量浪费一些随从。
(1)当没有剧毒时,又是当炮灰的。
先考虑一般嘲讽,continue;再考虑将圣盾嘲讽转化为一般嘲讽。
再考虑普通的,最后考虑圣盾。
(2)有剧毒时,要kill,但是要先浪费一些。
所以先将圣盾嘲讽转化为一般嘲讽,再考虑一般嘲讽。
再考虑圣盾,最后考虑普通的。

代码如下:

*/ 
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1010;
int n,a,b,c,d,v[N];
string s;

void solve(){
	scanf("%d%d%d%d%d",&n,&a,&b,&c,&d);
	int a1=a,b1=b,c1=c,d1=d;//用于求最大值 
	int a2=a,b2=b,c2=c,d2=d;//用于求最小值 
	int ma=0,mi=0;
	cin>>s;
	//求最大值 
	for(int i=0;i<s.size();i++){
		if(s[i]=='0'){
			if(d1>0) d1--,c1++;//当没有剧毒时,又因为要先攻击带嘲讽的,所以先攻击圣盾嘲讽,将其变为一般嘲讽 
			else if(c1>0) continue;//攻击嘲讽,没用 
			else if(b1>0) b1--,a1++;//再攻击圣盾,将其变为普通的 
		}
		else {//当有剧毒时,必然先攻击一般嘲讽的,其次考虑圣盾嘲讽;再优先考虑普通的,最后考虑圣盾 
			if(c1>0) c1--,ma++;
			else if(d1>0) d1--,c1++;
			else if(a1>0) a1--,ma++;
			else if(b1>0) b1--,a1++;
		}
	}
	
	//求最小值时 
	for(int i=0;i<s.size();i++){
		if(s[i]=='0'){//不带剧毒时 
			if(c2>0) continue;//因为要优先攻击带嘲讽的,又要使最小,所以浪费一个去攻击一般嘲讽的 
			else if(d2>0) d2--,c2++;//其次考虑圣盾嘲讽 
			else if(a1>0) continue;//再考虑一般的,反正都打不死,yyy 
			else if(b1>0) b1--,a1++; //最后考虑圣盾 
		}
		else{//有剧毒时 
			if(d2>0) d2--,c2++;//要求最小值,那么应该先考虑圣盾嘲讽,将其转化为一般嘲讽, 
			else if(c2>0) c2--,mi++;//然后再考虑一般嘲讽 
			else if(b2>0) b2--,a2++;//再考虑圣盾 
			else if(a2>0) a2--,mi++;//再考虑普通的 
		}
	}
	cout<<ma<<" "<<mi<<endl;
}

int main(){
	int t;
	cin>>t;
	while(t--){
		solve();
	}
	return 0;
}

5.你吓到我的马了.jpg

考察:BFS

思路
从马在的位置进行BFS,每个位置ans初始化为-1。
BFS时初始化马所在位置答案为0,加入队列。
然后依次取出队首,枚举8个方向。
如果没有越界并且没有到障碍点并且没有到被卡马脚的地方并且没有操作过,
将这个点加入队列,更新步数

代码如下:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1010;
int n,m,t;
char s[N][N];
int ans[N][N];
int dx[8]={1,1,-1,-1,2,2,-2,-2},dy[8]={2,-2,2,-2,1,-1,1,-1};//马走的斜日 
int tx[8]={0,0,0,0,1,1,-1,-1},ty[8]={1,-1,1,-1,0,0,0,0};
struct node{
	int x,y;
};
bool check(int x,int y){//判断是否越界 
	if(x<0||x>=n||y<0||y>=m) return false;
	return true;
}

void bfs(int x,int y){
	queue<node> q;//BFS的常规操作:队列 
	node p,mp;
	p.x=x,p.y=y;
	q.push(p);//入队 
	ans[x][y]=0;//马所在位置到所在位置的步数为0 
	while(!q.empty()){
		p=q.front();//取出队首元素 
		q.pop();
		for(int i=0;i<8;i++){//8个方向遍历 
			int xx=p.x+dx[i];
			int yy=p.y+dy[i];
			if(check(xx,yy)&&s[xx][yy]!='X'&&s[p.x+tx[i]][p.y+ty[i]]!='X'&&ans[xx][yy]==-1){//如果没有越界并且没有到障碍点并且没有到被卡马脚的地方并且没有操作过 
				mp.x=xx,mp.y=yy;
				q.push(mp);//入队 
				ans[xx][yy]=ans[p.x][p.y]+1;//步数加1 
			}
		}
	}
}

int main(){
	scanf("%d%d",&n,&m);
	memset(ans,-1,sizeof(ans));//初始化为-1 
	for(int i=0;i<n;i++) scanf("%s",s[i]);//一行一行输入 
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			if(s[i][j]=='M'){//马是起点,从这个点开始bfs 
				bfs(i,j);
			}
		} 
	}
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			printf("%d ",ans[i][j]);
		} 
		puts("");
	}
	return 0;
}

6.自闭

考察:模拟

思路:

设置的数组:
1.v数组记录此人是否提交过
2.h[][]数组记录此人此题WA的次数
3.dp[][]数组记录 此人此题是否AC
4.d[]记录此题AC的总数量
5.ma[][]数组记录此人此题连续WA的最大次数
6.cnt[]数组记录此人AC题的数量
代码如下:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5005;
int n,m,w,a[N],b[N],c[N],v[105],dp[105][15],h[105][15],d[105],ma[105][15],cnt[105]; 
int main(){
	scanf("%d%d%d",&n,&m,&w);
	for(int i=1;i<=w;i++){
		scanf("%d%d%d",&a[i],&b[i],&c[i]);
		v[a[i]]=1;
		if(c[i]==1){
			h[a[i]][b[i]]=0;//连续最大WA数清0 
			if(dp[a[i]][b[i]]==0){
				dp[a[i]][b[i]]=1;
				d[b[i]]++;//记录此题被AC的总数 
				cnt[a[i]]++;//记录此人AC题目总数 
			}
		}
		else{
			h[a[i]][b[i]]++;//记录WA的数量 
			ma[a[i]][b[i]]=max(ma[a[i]][b[i]],h[a[i]][b[i]]);//更新连续WA的最大值 
		}
	}
	for(int i=1;i<=n;i++){
			if(v[i]==0) {//如果没有提交过 
				printf("998244353\n");
				continue;
			}
	        if(cnt[i]==0){//如果AC总数为0 
	        	printf("1000000\n");
	        	continue;
			}
			ll ans=0;
			for(int j=1;j<=m;j++){
				if(d[j]>0&&dp[i][j]==0) ans+=20;//如果该题最后有人通过但这个选手没通过
				if(d[j]>=n/2&&dp[i][j]==0) ans+=10;//如果至少有n/2个人通过但是这个选手没通过 
				ans+=1ll*ma[i][j]*ma[i][j];//如果此人此题连续WA了ma[i][j]次 
				if(dp[i][j]==0) ans+=1ll*ma[i][j]*ma[i][j];//最后还没通过 
			}
			printf("%lld\n",ans);
    }
}

7.变大!

**考察:**动态规划,背包

思路:
最后的结果肯定是把序列分成若干个值相等的块。那么我们可以考虑对于一个长度为n的块,
它最后的成为值相等的块需要几步:
如果n是奇数,那么它的最大值两边数目要么都是奇数,要么都是偶数。
如果都是偶数,那么除了第一次覆盖了3个位置,剩余每次覆盖2个位置,一共n/2个操作。
如果都是奇数,那么以最大值位置为中心覆盖一次,然后两边剩余就都是偶数了,操作次数也是n/2;

所以用dp[i][j]表示弄完前i个数字,操作了j次得到的最大值,
然后枚举最后一个块的长度去转移即可。

操作:
最优解a[1…n] 肯定是分成若干段,每段最大值唯一,且最后这整段都会变成这个最大值。
长度为 L 的段需要的次数是 L/2,背包一下即可。

代码如下:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 60;
int a[N],n,dp[N][N];

void solve(){
	scanf("%d",&n);
	memset(a,0,sizeof(a));
	memset(dp,0,sizeof(dp));
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++){
		int ma = a[i];//记录此区间最大值 
		for(int j=i-1;j>=0;j--){ 
		    int c=(i-j)/2;//长度:i-j,此长度需要的次数为:(i-j)/2 
		    for(int k=0;k+c<=i;k++){//
		    	dp[i][k+c]=max(dp[i][k+c],dp[j][k]+ma*(i-j));
			}
			ma=max(ma,a[j]);//更新此段的最大值 
		}
	}
	for(int i=1;i<=n;i++) printf("%d ",dp[n][i]);
	puts("");
}

int main(){
	int t;
	cin>>t;
	while(t--){
		solve();
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值