目录

牛客NC16783 拼数

传送门

题意:将多个整数拼接成最大整数

思路:一开始直接想到的就是按字典序排序,仔细思考后发现没有那么简单,听了老师的课才想到可以把两个字符串先拼接起来再交换比较大小

也可以理解为,对于最优解字符串,相邻字符拼接起来一定是比两者交换后的结果大,同时不相邻的两字符串也满足(局部最优选择导向全局最优),故逆向的推出最优解的条件。

#include <bits/stdc++.h>
using namespace std;
int n;
string s[30];
bool cmp(string a,string b){
	string s1 = a + b;
	string s2 = b + a;
	return s1 > s2;
}
signed main(){
	scanf("%d",&n);
	for (int i=1;i<=n;i++) cin>>s[i];
	sort(s+1,s+1+n,cmp);
	for (int i=1;i<=n;i++) cout<<s[i];
}

cf489C Given Length and Sum of Digits...

传送门

题意:给定数字长度和数字每位加起来的总和,求最小数和最大数

思路一: 一开始想到的是对s进行分类讨论,小于等于9的是一类,大于9的是另一类,然后模拟时对最大数而言,最高位要放尽可能多的9,对最小数而言,则是末尾要放尽可能多的9,此时其实已经想到可以先求出最大数,再将其翻转求最小数,但是觉得麻烦作罢(事实证明这才是正解),于是我开始了漫长又虐心的分类讨论之路。。

#include <bits/stdc++.h>
using namespace std;
signed main(){
	int m,s;cin>>m>>s;
	if (s==0){//特判s等于0的情况
		if (m>1) cout<<-1<<" "<<-1;
		else {
			cout<<0<<" "<<0;
		}
		return 0;
	}
	int cnt = s / 9;//最多可以放几个9
	int snt = s - (cnt*9);//剩余数的和
	if (cnt>m){//特判放不满的情况
		cout<<-1<<" "<<-1;
		return 0;
	}
	string a(110,'0');
	string b(110,'0');
	if (s<=9){//分类讨论
		if (m==1){
			a[0] = char('0'+s);
		}
		else {
			a[0] = '1';
			for (int i=1;i<m-1;i++){
				a[i] = '0';
			}
			a[m-1] = char(s-1+'0');	
		}
		b[0] = char(s+'0');
		for (int i=1;i<m;i++){
			b[i] = '0';
		}
	}
	else {
		for (int i=1;i<=cnt;i++){
			a[m-i] = '9';
		}//末尾放9
		if (m-cnt-1>=1){//处理放完9之后的数 分类讨论还有几位可以放
			if (snt==0){//如果余数等于0 就要凑一个8出来
            //比如 000999 100899
				a[m-cnt] = '8';
				for (int i=1;i<m-cnt;i++) a[i] = '0';
				a[0] = '1';
				
			}
			else {//不为0直接放
				a[m-cnt-1] = char('0'+snt-1);
				for (int i=1;i<m-cnt-1;i++) a[i] = '0';
				a[0] = '1';	
			}
		}
		else if (snt){//只剩一位直接放余数
			a[0] = char('0'+snt);
		}
		
		for (int i=0;i<cnt;i++){
			b[i] = '9';
		}
		b[cnt] = char('0'+snt);
		for (int i=cnt+1;i<m;i++){
			b[i] = '0';
		}
	}
	int sum1 = 0,sum2 = 0;
	for (int i=0;i<m;i++){
		sum1 += (a[i]-'0');
		sum2 += (b[i]-'0');
	}
	if ((sum1!=s)||(sum2!=s)){
		cout<<-1<<" "<<-1<<endl;
		return 0;
	}
	for (int i=0;i<m;i++) cout<<a[i];
	cout<<" ";
	for (int i=0;i<m;i++) cout<<b[i];
	
}

思路二:求出最大数后,直接翻转,特判首位是否为0,为0就对其+1,对最高位不是0的数-1

#include <bits/stdc++.h>
using namespace std;
signed main(){
	int m,s;cin>>m>>s;
	if (s==0){
		if (m>1){
			cout<<-1<<" "<<-1;
		}
		else {
			cout<<0<<" "<<0;
		}
		return 0;
	}
	int cnt = s / 9;
	int snt = s - cnt * 9;
	if (cnt>m){
		cout<<-1<<" "<<-1;
	}
	else if (cnt==m&&snt){
		cout<<-1<<" "<<-1;
	}
	else {
		string a,b;
		for (int i=0;i<cnt;i++) b += '9';//高位放9
		if (snt) b += char(snt+'0'),cnt++;//这边记得cnt要加1
		for (;b.size()<m;) b += '0';//末尾补0
		a = b;
		reverse(a.begin(),a.end());
		if (a[0]=='0'){
			a[0] = '1';
			a[m-cnt] -= 1;//修改
		}
		cout<<a<<" "<<b;
	}
}

反思:WA了整整三四次才过的。第一是没有特判不能构造的情况,第二是在构造最小数的时候翻改了很多次,第三也是第一思路太过繁琐。。

牛客NC16618 排座椅

传送门

题意:给一系列相邻坐标,问怎样划分能最多将这些相邻坐标划分开来

思路:一开始无从下手,后来意识到横纵坐标其实是相互独立,那么就只要标记每一个横竖出现的次数,进行排序然后贪心的选择即可。

#include <bits/stdc++.h>
using namespace std;
int n,m,k,l,d;
struct Node{
    int wi;
    int pos;
};
Node x[2100],y[2100];
bool cmp(Node a,Node b){
    return a.wi > b.wi;
}
bool cmp1(Node a,Node b){
    return a.pos < b.pos;
}
signed main(){
	cin>>n>>m>>k>>l>>d;
    for (int i=0;i<=2001;i++){
        x[i].pos = i;
        y[i].pos = i;
    }
	while (d--){
		int xx,yy,xi,yi;cin>>xx>>yy>>xi>>yi;
		if (xx==xi){
			y[min(yy,yi)].wi++;
		}
		else {
			x[min(xx,xi)].wi++;
		}
	}
	sort(y,y+2001,cmp);
	sort(x,x+2001,cmp);
    
    sort(y,y+l,cmp1);
    sort(x,x+k,cmp1);
    
	for (int i=0;i<k;i++) {
		if (i) cout<<' ';
		cout<<x[i].pos;
	}
	cout<<'\n';
	for (int i=0;i<l;i++) {
		if (i) cout<<' ';
		cout<<y[i].pos;
	}
	cout<<'\n';
}

反思: 题意没看清要对输出进行排序,WA了好几发才过。。。

牛客NC200190 矩阵消除游戏

传送门

题意:给n行m列矩阵,每次可以选择一行或者一列消除,一共可以消除k次,问最多可以消除数的总和

思路:第一想法无脑排序,然后发现问题没有那么简单,尝试分类讨论,无果。。然后注意到数据范围,先暴力枚举怎么选择行数,再对列进行排序(一开始用dfs超时,最后用位运算思想过的)

超时代码:

//剪枝也可写 但是我还不会。。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,m,k,ans;
int a[20][20],b[20][20];
int dp[20];
bool vis[20];
void dfs(int step,int c){
	if (step==n||c==k){
		int sum = 0,cnt = 0,snt = 0;
		memcpy(b,a,sizeof(a));
		int h[20]={0};
		for (int i=1;i<=n;i++){
			printf("dp[%d] = %d\n",i,dp[i]);
			if (!dp[i]) continue;
			
			cnt++;
			for (int j=1;j<=m;j++){
				sum += b[i][j];
				b[i][j] = 0;
			}
		}
		//printf("sum = %d\n",sum);
		for (int i=1;i<=n;i++){
			for (int j=1;j<=m;j++){
				h[j] += b[i][j];
			}
		}
		sort(h+1,h+1+m,greater<int>());
		for (int i=1;i<=k-cnt;i++){
			sum += h[i];
		}
		ans = max(sum,ans);
		return;
	}
	for (int i=step;i<=n;i++){
		if (vis[i]) continue;
		vis[i] = 1;
		for (int j=0;j<2;j++){
			if (j==0){
				dp[i] = j;
				dfs(step+1,c);
			}
			else if (c+1<=k){
				dp[i] = j;
				dfs(step+1,c+1);
				dp[i] = 0;
			}
		}
		vis[i] = 0;
	}
}
signed main(){
	cin>>n>>m>>k;
	for (int i=1;i<=n;i++){
		for (int j=1;j<=m;j++){
			cin>>a[i][j];
		}
	}
	dfs(1,0);
	cout<<ans;
}

bitset:

#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,m,k,ans;
int a[20][20],b[20][20];
int dp[20];
bool vis[20];
void solve(int x){
	int t = 0,cnt = 0;
	memset(dp,0,sizeof(dp));
	while (x>0){
		t++;
		if (x&1) dp[t] = 1,cnt++;
		else dp[t] = 0;//这边不小心把分号写成逗号,一直死循环(捂脸
		x >>= 1;
	}
	//for (int i=1;i<=n;i++) printf("dp[%d] = %d\n",i,dp[i]);
	if (cnt<=k){
		int sum = 0,snt = 0;
		memcpy(b,a,sizeof(a));
		int h[20]={0};
		for (int i=1;i<=n;i++){
			//printf("dp[%d] = %d\n",i,dp[i]);
			if (!dp[i]) continue;
			//cnt++; 这边cnt++没备注 导致后面有几个点一直没过。。
			for (int j=1;j<=m;j++){
				sum += b[i][j];
				b[i][j] = 0;
			}
		}
		//printf("sum = %d\n",sum);
		for (int i=1;i<=n;i++){
			for (int j=1;j<=m;j++){
				h[j] += b[i][j];
			}
		}
		sort(h+1,h+1+m,greater<int>());
		for (int i=1;i<=k-cnt;i++){
			sum += h[i];
		}
		ans = max(sum,ans);
		return;
	}
}
signed main(){
	cin>>n>>m>>k;
	for (int i=1;i<=n;i++){
		for (int j=1;j<=m;j++){
			cin>>a[i][j];
		}
	}
	for (int i=0;i<=((1<<n)-1);i++){
		solve(i);
	}
	cout<<ans;
}

反思:修改代码要再检查一遍,然后以后对dfs还要再练习

牛客NC16561 国王的游戏

传送门

题意:经典贪心题(因为都是中文懒得概括了(x

思路:通过假设最优解列出相关公式,发现左右手乘积值排序后是最优解 (详细思路以后再补

#include <bits/stdc++.h>//这个代码只能得六十,因为题目数据范围需要开高精度
#define int long long
using namespace std;
int n,a,b,ans;
struct Node{
	int l,r,w;	
};
Node x[1100];
bool cmp(Node u,Node v){
	return u.w < v.w;
}
signed main(){
	scanf(" %lld",&n);
	scanf(" %lld%lld",&x[0].l,&x[0].r);
	for (int i=1;i<=n;i++){
		scanf(" %lld%lld",&x[i].l,&x[i].r);
		x[i].w = x[i].l * x[i].r;
	}
	sort(x+1,x+1+n,cmp);
	int cnt = 1;
	for (int i=1;i<=n;i++){
		cnt = cnt * x[i-1].l;
		ans = max(ans,cnt/x[i].r);
	}
	cout<<ans;
}

反思:通过交换寻找贪心的最优策略是常见套路

牛客NC25043 Protecting the Flowers

传送门

题意:有n头牛,回它所在的牛棚单程所用时间是ti,在花田等待时会对花田单位时间内破坏di的花,问怎样安排顺序时得对花田破坏最小

思路:和上题同理推出贪心策略公式 t[a]*d[b] < t[b] * d[a]

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 7;
struct Node{
	int t,d;
}a[N];
int n,ans,sum;
bool cmp(Node u,Node v){
	return u.t * v.d < v.t * u.d; 
}
signed main(){
	scanf(" %lld",&n);
	for (int i=1;i<=n;i++){
		scanf(" %lld%lld",&a[i].t,&a[i].d);
	}
	sort(a+1,a+1+n,cmp);
	for (int i=1;i<=n;i++){
		ans += a[i].d * sum;
		sum += a[i].t * 2;
	}
	cout<<ans;
}

牛客NC18979 毒瘤xor

传送门

题意:略

思路:发现答案数字的二进制决策取决于所选区间数每位0,1的个数,故用前缀和维护 (不能无脑开long long ,会mle

#include <bits/stdc++.h>
using namespace std;
int n,q;
const int N = 1e5 + 7;
int a[N],sum[N][60],ans[60];
void dig(int t,int x){
	for (int i=0;i<40;i++){
		sum[t][i] = sum[t-1][i] + ((x&(1<<i))==0?0:1);
	}
	return;
}
void query(int l,int r){
	int cnt = 0;
	for (int i=0;i<31;i++){
		int len = sum[r][i] - sum[l-1][i];
		//cout<<len<<endl;
		if(len<r-l+1-len){
			cnt += (1<<i);
		}
	}
	printf("%d\n",cnt);
	return;
}
signed main(){
	scanf(" %d",&n);
	for (int i=1;i<=n;i++) scanf(" %d",a+i),dig(i,a[i]);
	scanf(" %d",&q);
	while (q--){
		int l,r;scanf(" %d%d",&l,&r);
		query(l,r);
	}
}

牛客NC20860 兔子的区间密码

传送门

题意:给定区间任选两个数求最大异或值

思路:一开始把最大值固定住,变换最小值,很快想到要找第一位不同的,但没想到最大值也可以调,于是想了很久还是错的;最后才意识到找到第一位不同后,最大和最小值都可以进行调整并凑出一,故答案:(1<<pos)-1

}#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main(){
	int n;scanf(" %lld",&n);
	while (n--){
		int x,y;scanf(" %lld%lld",&x,&y);
		if (x==y){
			printf("%lld\n",(x^y));
		}
		else {
			int pos,ans;
			for (int i=0ll;;i++){
				if ((x>>i)==(y>>i)){
					pos = i;
					break;
				}
			}
			ans = (1ll<<pos)-1ll;(1后面加ll转换成长整型
			cout<<ans<<endl;
		}
	}
}

牛客NC17857 起床困难综合症

传送门

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;
string op[N];
int t[N];
signed main(){
	int n,m;scanf(" %d%d",&n,&m);
	int a = 0,b = (1<<29) - 1;
	for (int i=0;i<n;i++){
		cin>>op[i]>>t[i];
		if (op[i]=="AND") a &= t[i],b &= t[i];
		if (op[i]=="OR") a |= t[i],b |= t[i];
		if (op[i]=="XOR") a ^= t[i],b ^= t[i]; 
	}
	bool ok = 0;
	int ans = 0;
	for (int i=29;i>=0;i--){
		int x = 1 << i;
        if ((a&x)!=0) continue;
		if ((b&x)!=0&&ans+x<=m){
			ans += x;
		}
	}
	for (int i=0;i<n;i++){
		if (op[i]=="AND") ans &= t[i];
		if (op[i]=="OR") ans |= t[i];
		if (op[i]=="XOR") ans ^= t[i]; 
	}
	cout<<ans;
}