【笔记整理】Codeforces Round #708 (Div. 2)A-E1

A题:Meximization(最大化)
题意:输入数组,输出数组,使从左到右依次求子数组的MEX之和最大。
(题意看题目所给注释将更好理解)
思路:桶排序。根据原数组尽可能按照0,1,2…的顺序将前面填满,使每一次取得的MEX最大,按任意顺序输出未输出过的数字。
分类:贪心800
通过代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[105];
//下标代表数字,值代表该数字存了几个 
int main() {
	int t;
	scanf("%d",&t);//t组数据 
	while(t--) {
		memset(a,0,sizeof(a));//注意每次初始化 
		int n;
		scanf("%d",&n);
		int x;
		for(int i=0; i<n; i++) {
			scanf("%d",&x);
			a[x]++;//桶排序 
		}
		
		for(int i=0; i<=100; i++) //优先输出一次0-100 
			if(a[i]>0) {
				printf("%d ",i);
				a[i]--;
			}
		
		for(int i=0; i<=100; i++) //输出未输出过的数字 
			for(int j=0; j<a[i]; j++)
				printf("%d ",i);
	}
	return 0;
}

B题:M-arrays(M-数组)
题意:输入数组,按照要求最少分成几个新数组。要求:要么数组中只有一个数,要么数组中的相邻的两个数相加之和能被m整除。
思路:将输入数字进行求模预处理,桶排序为0,1…m-1,此时0单独分为一组,1与m-1为一组,2与m-2为一组…以此类推。
然后开始判断“0”组:如果存在count++;
开始判断1~m-1组:
1.如果1与m-1都没有,count不变。
2.如果差的绝对值小于等于1,count++;
3.如果差的绝对值大于1,count+=两者差的绝对值;
注意特殊判断当m为偶数时,m/2无配对的情况
(tourist的奇妙代码ans+=max(0,|cntx−cntm−x|−1))
分类:构造1200
通过代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[100005],b[100005];
int main() {
	int t;
	scanf("%d",&t);
	while(t--) {
		int n,m;
		scanf("%d%d",&n,&m);
		memset(b,0,sizeof(b));
		for(int i=1; i<=n; i++) {
			scanf("%d",&a[i]);
			a[i]=a[i]%m;//预处理
		}
		int count1=0;//计数器初始化 
		for(int i=1; i<=n; i++) {
			b[a[i]]++;//桶排序 
		}
		if(b[0]>0) {
			count1++;//统计"0"组 
		}
		if(m%2==0) {//很抱歉我的代码还分了奇偶
			//实际上完全不用这么麻烦
			count1+=min(b[m/2],1);
			//这是偶数比奇数多的一行代码 
			for(int i=1; i<m/2; i++) {//统计1~m-1对应组 
				if(b[i]==0&&b[m-i]==0) {//都等于0就不计 
				} else if(abs(b[i]-b[m-i])<=1) {
					count1++;//相差为1或小于1就计为1 
				} else {
					count1+=abs(b[i]-b[m-i]);
				}
			}
		} else {//奇数剩余部分相同 
			for(int i=1; i<=m/2; i++) {
				if(b[i]==0&&b[m-i]==0) {
				} else if(abs(b[i]-b[m-i])<=1) {
					count1++;
				} else {
					count1+=abs(b[i]-b[m-i]);
				}
			}
			if(m==1)count1=1;
			//注意特殊判断如果m=1的情况
			//有重判的可能 
		}
		printf("%d\n",count1);
	}
	return 0;
}

C1题:k-LCM (easy version)—K-最小公倍数(简单版本)
题意:输入n,k=3(简单版本),输出满足要求的数组a。要求:
1.a1+a2+…+ak=n
2.LCM(a1,a2,…,ak)≤n/2
思路:写了几项以后发现规律:
1.如果n%4=0,输出(n/2,n/4,n/4)。
2.如果n%2=0,输出(n/2−1,n/2−1,2).(1,⌊n/2⌋,⌊n/2⌋)。
3.如果n%2=1,输出(1,⌊n/2⌋,⌊n/2⌋)。
分类:找规律1200
通过代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
	int t;
	scanf("%d",&t);
	while(t--) {
		int n,k;
		scanf("%d%d",&n,&k);
		if(n%2==1) {//如果是奇数 
			printf("1 %d %d\n",(n-1)/2,(n-1)/2);
		} else {
			if(n%4==0) {//如果被4整除 
				printf("%d %d %d\n",n/2,n/4,n/4);
			} else {//如果只被2整除 
				printf("2 %d %d\n",(n-2)/2,(n-2)/2);
			}
		}
	}
	return 0;
}

C2题:k-LCM (hard version)(更简单版本(雾))
题意:输入n,k,输出满足要求的数组a。要求:
1.a1+a2+…+ak=n
2.LCM(a1,a2,…,ak)≤n/2
思路:发现如果我们先输出若干个1,不改变LCM的同时又能使得k=3,可令题目又变回C1,此时n=n-k+3。
分类:找规律1600
通过代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
	int t;
	scanf("%d",&t);
	while(t--) {
		int n,k;
		scanf("%d%d",&n,&k);
		for(int i=0;i<k-3;i++){//先输出若干个1 
			printf("1 ");
		}
		n=n-k+3;//注意改变n值 
		if(n%2==1) {//输出同C1题 
			printf("1 %d %d\n",(n-1)/2,(n-1)/2);
		} else {
			if(n%4==0) {
			printf("%d %d %d\n",n/2,n/4,n/4);
			} else {
			printf("2 %d %d\n",(n-2)/2,(n-2)/2);		
			}
		}
	}
	return 0;
}

D题:Genius(天才)
题意:(纯翻译)
有n个问题用1到n的整数编号。第i个问题的复杂性为ci=2^i、标记tagi和分数si。
在解决问题i之后,当且仅当IQ<| ci−cj |和tagi≠tagj时,才允许解决问题j。解决这个问题后,你的智商会改变,变成智商=| ci−cj |,你会得到| si−sj |分。
任何问题都可能是第一个。你可以按任何顺序和次数解决问题。
最初你的智商是0。找出可以获得的最大点数。
思路:维护一个DP数组,代表当前点能获得的最大分数。
每新添一个问题,可以从已有的任何旧问题来解决新问题,同时也可以从新位置跳回原位置。
奇怪的状态转移方程:
dp[i] = max(dp[i], dpj + p);
dp[j] = max(dp[j], dpi + p);
分类:动态规划2500
通过代码:

#include<bits/stdc++.h>
using namespace std;
int main() {
	int T;
	cin >> T;
	while (T--) {
		int n;
		cin >> n;
		vector<long long> s(n), tag(n), dp(n, 0);//注意初始化数组
		//注意dp数组代表从下标位置开始,所能获得的最大分数 
		for (int i = 0; i < n; ++i)
			cin >> tag[i];
		for (int i = 0; i < n; ++i)
			cin >> s[i];
		for (int j = 1; j < n; ++j) {//dp[x<=j]范围内存储当前最优决策 
			for (int i = j - 1; i >= 0; --i) {
				if (tag[i] == tag[j]) continue;	//如果tag相同则不能转移
				long long dpi = dp[i], dpj = dp[j];//保留原来dp[i]值
				//因为dp[j]的值需要由dp[i]推导
				//新的dp[i]值已经被dp[j]更新了 
				long long p = abs(s[i] - s[j]);//获得分数 
				dp[i] = max(dp[i], dpj + p);
				//每新添一个问题,可以从已有的任何旧问题来解决新问题
				//例如{i=0,j=1}时,这条转移方程代表从i到j获得的分数 
				dp[j] = max(dp[j], dpi + p);
				//同时,j也可以跳回i位置 
			}
		}
		cout << *max_element(dp.begin(), dp.end()) << endl;
		//求范围内最大值并返回 
	}
	return 0;
}

E1题:Square-free division (easy version) —平方自由分割(简单版本)
题意:输入数组,输出满足要求的最少目标组数。要求:
连续的每一组任意两数的乘积不是平方数
思路
1.线性筛预处理数据找出最小质因数
2.处理每个数字中可以被平方的因数
3.一个指针标记已成组的数字,另一指针推进遍历
分类:线性筛 1700
通过代码:

#include <bits/stdc++.h>
using namespace std;
const int MAXA = 1e7;
vector<int> primes;
int mind[MAXA + 1]; 
int main() {
	//功能1:线性筛预处理数据找出最小质因数
	//mind数组存一个(从2开始)最小的质因数
    for (int i = 2; i <= MAXA; ++i) {
        if (mind[i] == 0) {//如果没有因数,i为质数 
            primes.emplace_back(i);//质数数组后插新的质数 
            mind[i] = i;//质因数就是自己 
        }
        for (auto &x : primes) {//遍历质数数组 
            if (x > mind[i] || x * i > MAXA) break;
			//合法且在适当范围内
            mind[x * i] = x;//线性筛 
        }
    }
    
    //功能2:处理每个数字中可以被平方的因数
    int T;
    cin >> T;
    while (T --> 0) {
        int n, k;
        cin >> n >> k;
        vector<int> a(n, 1);//定义n个数组初始化为1 
        for (int i = 0; i < n; ++i) {
            int x;
            cin >> x;
            int cnt = 0, last = 0;//初始化为0 
            while (x > 1) {//分解质因数 
                int p = mind[x];//p为当前最小因数 
                if (last == p) {//如果与最后的质因数相同 
                    ++cnt;//则将cnt原标记清除 
                    //例如x=18=2*3*3此时的两个3可以被消除 
                } else {
                    if (cnt % 2 == 1) a[i] *= last;
					//如果产生新的质因数,则旧的质因数无法约去
					//保留进a数组内 
                    last = p;//最后一个质因数
                    cnt = 1;//当前有一个这样的质因数last 
                }
                x /= p;
            }
            if (cnt % 2 == 1) a[i] *= last;
			//不要忘记处理奇数个未处理的last 
        }

		//功能3:一个指针标记已成组的数字,另一指针推进遍历
        int L = 0, ans = 1;//L代表左界,位置<L代表已成一组 
        map<int, int> last;//存储对应的质因数与最新位置
        for (int i = 0; i < n; ++i) {
            if (last.find(a[i]) != last.end() && last[a[i]] >= L) {
            	//如果找到了相同的a[i],则产生新的一组 
            	//并不会被已成一组的影响 
                ++ans;
                L = i;//先更新左界,再重置相同的last代表位置 
            }
            last[a[i]] = i;//将当前的a[i]插入map
            //注意同时也是更新操作 
        }
        cout << ans << endl;
    }
    return 0;
}

E2题:Square-free division (hard version) —平方自由分割(困难版本)
(太难了无题解)
分类:动态规划 2500
通过代码:

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

const int INF = 1e9 + 1;
const int MAXA = 1e7;
vector<int> primes;
int mind[MAXA + 1];

int main() {
    for (int i = 2; i <= MAXA; ++i) {
        if (mind[i] == 0) {
            primes.emplace_back(i);
            mind[i] = i;
        }
        for (auto &x : primes) {
            if (x > mind[i] || x * i > MAXA) break;
            mind[x * i] = x;
        }
    }
    vector<int> cnt(MAXA + 1);
    int t;
    cin >> t;
    while (t--) {
        int n, k;
        cin >> n >> k; // уже k манулов
        vector<int> a(n, 1);
        for (int i = 0; i < n; ++i) {
            int x;
            cin >> x;
            int cnt = 0, last = 0;
            while (x > 1) {
                int p = mind[x];
                if (last == p) {
                    ++cnt;
                } else {
                    if (cnt % 2 == 1) a[i] *= last;
                    last = p;
                    cnt = 1;
                }
                x /= p;
            }
            if (cnt % 2 == 1) a[i] *= last;
        }
        vector<vector<int>> mnleft(n, vector<int>(k + 1));
        for (int j = 0; j <= k; j++) {
            int l = n, now = 0;
            for (int i = n — 1; i >= 0; i--) {
                while (l — 1 >= 0 && now + (cnt[a[l — 1]] > 0) <= j) {
                    l--;
                    now += (cnt[a[l]] > 0);
                    cnt[a[l]]++;
                }
                mnleft[i][j] = l;
                if (cnt[a[i]] > 1) now--;
                cnt[a[i]]--;
            }
        }
        vector<vector<int>> dp(n + 1, vector<int>(k + 1, INF));
        for (auto &c : dp[0]) c = 0;
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= k; j++) {
                if (j > 0) dp[i][j] = dp[i][j — 1];
                for (int lst = 0; lst <= j; lst++) {
                    dp[i][j] = min(dp[i][j], dp[mnleft[i — 1][lst]][j — lst] + 1);
                }
            }
        }
        int ans = INF;
        for (auto &c : dp.back()) ans = min(ans, c);
        cout << ans << "\n";
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值