1.21Codeforces Round 999 (part div.2)
前言:质量超级高的div.2
B:
题意:
给你\(n\)个数,问是否可以在其中选出4个使得构成一个等腰梯形,如果可以就输出方案,否则输出\(-1\)
思路:
考虑可以构成等腰梯形的充要条件:设此等腰梯形的两个腰长度为\(c\),两个底为\(a,b\),则\(|a-b| < 2c\)
若存在至少两对相同的边长,则其一定能构成等腰梯形
若只有一对,则根据充要条件判断
否则无解
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
const int N = 2e5 + 10;
int a[N];
void solve(){
int n;
std::cin >> n;
for(int i = 1;i <= n;++i){
std::cin >> a[i];
}
std::sort(a + 1,a + n + 1);
for(int i = n;i >= 4;--i){
if(a[i] == a[i - 3]){
std::cout << a[i] << ' ' << a[i] << ' ' << a[i] << ' ' << a[i] << '\n';
return;
}
}
std::set<int> s;
for(int i = n;i >= 2;--i){
if(a[i] == a[i - 1]) s.insert(a[i]);
}
if(s.empty()) {
std::cout << -1 << '\n';
return;
}
if((int)s.size() >= 2){
auto it = s.begin();
int x = *it;
++it;
int y = *it;
std::cout << x << ' ' << x << ' ' << y << ' ' << y << '\n';
return;
}
int x = *s.begin();
int p = (int)(std::lower_bound(a + 1,a + n + 1,x) - a);
for(int i = p;i <= n - 2;++i){
a[i] = a[i + 2];
}
n -= 2;
for(int i = 2;i <= n;++i){
if(a[i] - a[i - 1] < 2 * x){
std::cout << a[i - 1] << ' ' << x << ' ' << x << ' ' << a[i] << '\n';
return;
}
}
std::cout << -1 << '\n';
}
int main(){
int T;
std::cin >> T;
while(T--) solve();
return 0;
}
C:(小清新动态规划题)
题意:题面
转化题意:
找到一组 诚实 / 说谎 的分配, 使得没有两个说谎者可以站在一起, 并且满足所有诚实的人说的都是真的
思路:
考虑令\(f_{i,0/1}\)表示考虑到\(i\)人, 其中这个人是诚实还是说谎,考虑转移, 可以确定的是不能记录当前有多少个说谎者, 考虑找性质使其可以直接解决
重要性质:
若当前人说谎,则其左边的人一定是诚实的
那么考虑当前是诚实的人
1.\(a_{i} = a_{i-1}\)
那么若左边的人是诚实的,则满足题意;若左边的人是说谎的,那么由题意,左边的左边那个人一定是诚实的,所以若\(a_{i-2} + 1 = a_{i}\) 这种情况才可以成立
2.\(a_{i}\neq a_{i-1}\)
那么若左边的人是诚实的,则一定不满足题意;若左边的人是说谎的,那么由题意,左边的左边那个人一定是诚实的,所以若\(a_{i - 2} + 1 = a_{i}\)这种情况才可以成立
由上述条件,\(f\)的转移就十分简单了
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
const int MAXN = 2e5 + 20;
const int MOD = 998244353;
namespace calc {
int add(int a, int b) { return a + b > MOD ? a + b - MOD : a + b; }
void addon(int &a, int b) { a = add(a, b); }
} using namespace calc;
int n;
int a[MAXN];
int dp[MAXN][2];
void solve(){
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
memset(dp, 0, sizeof dp);
dp[0][0] = 1;
for (int i = 1; i <= n; i++) {
addon(dp[i][1], dp[i - 1][0]);
if (a[i] == a[i - 1]) {
if (a[i - 2] + 1 == a[i]) addon(dp[i][0], dp[i - 1][1]);
addon(dp[i][0], dp[i - 1][0]);
} else {
if (a[i - 2] + 1 == a[i]) addon(dp[i][0], dp[i - 1][1]);
}
}
printf("%d\n", add(dp[n][0], dp[n][1]));
}
int main()
{
int T;
scanf("%d", &T);
while (T--) solve();
return 0;
}
D:(逆向思维好题)
题意:题面
思路:
我们发现,如果真的按题意模拟合并数组\(a\),处理起来非常复杂,正难则反,若我们考虑将\(b\)数组中的元素拆分之后与\(a\)再比较会舒服很多,因为对于一个数\(x\),若要满足题意限制,则一定只能拆分成\(\lfloor\frac{x}{2}\rfloor\)与\(\lceil\frac{x}{2}\rceil\)这样的形式
我们可以对\(b\)中的数字进行这种拆分操作,正好\(n-m\)次。如果\(b\)中的最大数字也出现在\(a\)中,我们可以同时将其从\(a\)和\(b\)中删除。否则,\(b\)中最大的数字必须拆分为两个较小的数字
为了有效地管理\(a\)和\(b\)中的数字,我们可以使用优先队列或多重集。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<set>
#include<vector>
#define int long long
const int N = 2e5 + 10;
int a[N],b[N];
void solve(){
int n,m,sa = 0,sb = 0;
std::cin >> n >> m;
for(int i = 1;i <= n;++i) {
std::cin >> a[i];
sa += a[i];
}
for(int i = 1;i <= m;++i) {
std::cin >> b[i];
sb += b[i];
}
if(sa != sb){
std::cout << "No" << '\n';
return;
}
std::multiset<int> s;
for(int i = 1;i <= n;++i) s.insert(a[i]);
for(int i = 1;i <= m;++i){
std::vector<int> stk;
stk.push_back(b[i]);
while(!stk.empty()){
int y = stk.back();
stk.pop_back();
if(s.find(y) != s.end()){
s.erase(s.find(y));
}else{
if(y == 1){
std::cout << "No" << '\n';
return;
}
stk.push_back(y / 2);
stk.push_back(y - y / 2);
}
}
}
std::cout << "Yes" << '\n';
}
signed main(){
int T;
std::cin >> T;
while(T--) solve();
return 0;
}
E:(数学好题)
题意:题面
思路:
这道题其实如果严谨做出来并且在考场上A掉,那么其一定具有较高的数学素养,但是很可惜,大部分人都是赌王,直接在考场上赌出贪心思路,甚至有人根本没发现自己算法的不严谨性
因为最终答案是让你求出\(min\{\sum a_{i}\}\)所以我们希望可以每次都使\(a_{i}\)尽可能减去更多的值,有人问了,你怎么确定每次\(a_{i}\)一定不会增加呢?这其实就涉及到了按位与运算的性质,很好证明
那么好,我们令\(g_{i,j}\)表示对于\(a_{i}\)我们执行\(j\)次操作后,\(a_{i}\)的最小值,所以显然的,因为\(m\)的范围很小,我们可以直接状态压缩集合来暴力预处理。
现在,我们给出一个非常重要的结论(很多人考场根本没有发现自己这一部分漏洞):
首先为了方便,对于每一个\(a_{i}\)其代价函数\(g\)我们直接表示为\(g_{i}(1 < i \le m)\)
那么一定有:
也就是函数\(g\)呈凸性
proof(太妙了):
令\(S\)为产生\(g_{i-1}\)的操作集合(\(|S|=i-1\)),\(T\)为产生\(g_{i+1}\)的操作集合(\(|T|=i+1\)),令\(g_{i - 1}\)与\(g_{i + 1}\)的最高不同位为\(w\),则由按位与操作的性质,一定有\(g_{i-1}\)这一位是\(1\),而\(g_{i+1}\)是\(0\),(有用且有趣的性质,想一想为什么)。
令函数\(f(S)\)表示对于这一个\(a_{i},(|S| = i)\),操作集合\(S\)产生的值,所以形式化的,我们可以给出下列式子:
所以显然注意到:
有了这些铺垫,我们又注意到一定\(\exists_{y\in T-S}\)(这里的\(T-S\)表示集合的差)使得\(g_{i - 1}\)的第\(w\)位变成\(0\),且\(|S\bigcup \{y\}|=i\)。
所以一定有:
又因为此时\(f(S\bigcup \{y\})\)与\(g_{i+1}\)最多只在第\((w-1)\)位不同所以又有:
联立上述两个不等式我们可以得到以下放缩:
即:
Q.E.D
那么现在我们就可以继续流畅地解决这道题了我们发现对于数组\(g_{i}\)可以做差分操作,即:\(g_{i,j}=g_{i,j}-g_{i,j-1}\)又因为\(g\)刚刚证明过的凸性,可以得出对于每一个\(i\),\(g_{i}\)的差分序列是单调递减的,所以我们才能放心的把所有的\(g\)的差分序列放在一堆,然后按倒序排序,用\(\sum a_{i}\)减去前\(k\)个最小的即可。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
#define int long long
#define count_1 __builtin_popcount
using namespace std;
const int N = 1e5;
const int M = 15;
const int INF = 0x3f3f3f3f3f3f3f;
int a[N],b[M],val[N * 10 + 5],f[1 << 10],pre[N];
bool cmp(int a,int b){
return a > b;
}
void solve(){
int n,m,mx;
std::cin >> n >> m >> mx;
for(int i = 1;i <= n;++i) std::cin >> a[i];
for(int i = 1;i <= m;++i) std::cin >> b[i];
for(int i = 1;i <= n * m;++i) val[i] = 2e9;
for(int S = 1;S < (1 << m);++S){
pre[S] = (1ll << 31) - 1;
for(int i = 1;i <= m;++i)if((S >> i - 1 ) & 1) pre[S] &= b[i];
}
for(int i = 1;i <= n;++i){
f[0] = a[i];
for(int S = 1;S < (1 << m);++S){
f[S] = a[i] & pre[S];
int pos = (i - 1) * m + count_1(S);
val[pos] = min(val[pos],f[S]);
}
for(int j = i * m;j > (i - 1) * m + 1;--j) val[j] = val[j - 1] - val[j];
val[(i - 1) * m + 1] = a[i] - val[(i - 1) * m + 1];
}
int tot = n * m;
std::sort(val + 1,val + 1 + tot,cmp);
int ans = 0;
for(int i = 1;i <= n;++i) ans += a[i];
for(int i = 1;i <= mx;++i) ans -= val[i];
std::cout << ans << '\n';
}
signed main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr), std::cout.tie(nullptr);
int T;
std::cin >> T;
while(T--) solve();
return 0;
}
F1:
这个没什么特别惊艳的,直接给代码吧。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
const int N = 4e5 + 10;
int n,m1,m2,lens[N],lent[N];
void solve(){
std::string s,t;
std::cin >> s >> t;
n = s.length();
char u = t[0],v = t[n - 1];
s = u + s + v,t = u + t + v,n += 2;
int tmp_len = 0;
m1 = m2 = 0;
for(int i = 0;i < n;++i){
if(i > 0 && s[i] != s[i - 1]) lens[++m1] = tmp_len,tmp_len = 1;
else ++tmp_len;
}
lens[++m1] = tmp_len;
tmp_len = 0;
for(int i = 0;i < n;++i){
if(i > 0 && t[i] != t[i - 1]) lent[++m2] = tmp_len,tmp_len = 1;
else ++tmp_len;
}
lent[++m2] = tmp_len;
int i = 1,j = 1;
while(j <= m2){
if(i > m1) {
std::cout << -1 << '\n';
return;
}
if(lent[j] < lens[i]){
std::cout << -1 << '\n';
return;
}
if(lent[j] == lens[i]){++i,++j;continue;}
if(i == m1){
std::cout << -1 << '\n';
return;
}
int x = lens[i],y = lens[i + 1];
if(j + 1 <= m2){
while(i + 3 <= m1){
x += lens[i + 2],y += lens[i + 3],i += 2;
if(x > lent[j]) {
std::cout << -1 << '\n';
return;
}
if(x == lent[j]) break;
}
if(x == lent[j]) ++i,++j,lens[i] = y;
else {
std::cout << -1 << '\n';
return;
}
}else{
std::cout << -1 << '\n';
return;
}
}
if(i > m1) std::cout << (m1 - m2) / 2 << '\n';
else {
std::cout << -1 << '\n';
return;
}
}
int main(){
int T;
std::cin >> T;
while(T--) solve();
return 0;
}

浙公网安备 33010602011771号