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;
}