1.21Codeforces Round 999 (part div.2)

前言:质量超级高的div.2

B:

题意:

给你n个数,问是否可以在其中选出4个使得构成一个等腰梯形,如果可以就输出方案,否则输出1

思路:

考虑可以构成等腰梯形的充要条件:设此等腰梯形的两个腰长度为c,两个底为a,b,则|ab|<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:(小清新动态规划题)

题意:题面

转化题意:

找到一组 诚实 / 说谎 的分配, 使得没有两个说谎者可以站在一起, 并且满足所有诚实的人说的都是真的

思路:

考虑令fi,0/1表示考虑到i人, 其中这个人是诚实还是说谎,考虑转移, 可以确定的是不能记录当前有多少个说谎者, 考虑找性质使其可以直接解决

重要性质:
若当前人说谎,则其左边的人一定是诚实的

那么考虑当前是诚实的人

1.ai=ai1

那么若左边的人是诚实的,则满足题意;若左边的人是说谎的,那么由题意,左边的左边那个人一定是诚实的,所以若ai2+1=ai 这种情况才可以成立

2.aiai1

那么若左边的人是诚实的,则一定不满足题意;若左边的人是说谎的,那么由题意,左边的左边那个人一定是诚实的,所以若ai2+1=ai这种情况才可以成立

由上述条件,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,若要满足题意限制,则一定只能拆分成x2x2这样的形式

我们可以对b中的数字进行这种拆分操作,正好nm次。如果b中的最大数字也出现在a中,我们可以同时将其从ab中删除。否则,b中最大的数字必须拆分为两个较小的数字

为了有效地管理ab中的数字,我们可以使用优先队列或多重集。

#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{ai}所以我们希望可以每次都使ai尽可能减去更多的值,有人问了,你怎么确定每次ai一定不会增加呢?这其实就涉及到了按位与运算的性质,很好证明

那么好,我们令gi,j表示对于ai我们执行j次操作后,ai的最小值,所以显然的,因为m的范围很小,我们可以直接状态压缩集合来暴力预处理。

现在,我们给出一个非常重要的结论(很多人考场根本没有发现自己这一部分漏洞):

首先为了方便,对于每一个ai其代价函数g我们直接表示为gi(1<im)

那么一定有:

2gi<gi1+gi+1

也就是函数g呈凸性

proof(太妙了):

S为产生gi1的操作集合(|S|=i1),T为产生gi+1的操作集合(|T|=i+1),令gi1gi+1的最高不同位为w,则由按位与操作的性质,一定有gi1这一位是1,而gi+10,(有用且有趣的性质,想一想为什么)。

令函数f(S)表示对于这一个ai,(|S|=i),操作集合S产生的值,所以形式化的,我们可以给出下列式子:

gi=min|S|=i{f(S)}

所以显然注意到:

S,|S|=i f(S)gi

有了这些铺垫,我们又注意到一定yTS(这里的TS表示集合的差)使得gi1的第w位变成0,且|S{y}|=i
所以一定有:

gi1f(S{y})2w

又因为此时f(S{y})gi+1最多只在第(w1)位不同所以又有:

f(S{y})gi+12w1

联立上述两个不等式我们可以得到以下放缩:

2gi2f(S{y})<gi1+gi+1

即:

2gi<gi1+gi+1

Q.E.D

那么现在我们就可以继续流畅地解决这道题了我们发现对于数组gi可以做差分操作,即:gi,j=gi,jgi,j1又因为g刚刚证明过的凸性,可以得出对于每一个igi的差分序列是单调递减的,所以我们才能放心的把所有的g的差分序列放在一堆,然后按倒序排序,用ai减去前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;
}

F2:(It's so difficult that I think I should depend on my father's power.)

题意:题面

思路:

作者:YhjOI

出处:https://www.cnblogs.com/YhjOI/p/18751881

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   AxB_Thomas  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示