Codeforces Global Round 17

本文介绍了四道算法题目,包括最少询问次数确定特殊位置、判断数组是否能通过删除一个数字变成回文、最大邀请人数满足条件的宴会、计算子序列和为零的个数以及如何定向图中的边以最大化好点数量。涉及欧几里得距离、回文判断、二分查找、贝祖定理和图的遍历等算法思想。

A.Anti Light’s Cell Guessing

题目描述

​ 一个n×mn\times mn×m的矩形,有一个位置(i,j)(i,j)(i,j)是特殊的。

每次可以询问一个点(x,y)(x,y)(x,y),返回(i,j)(i,j)(i,j)(x,y)(x,y)(x,y)的欧几里得距离,问至少要询问多少次对任意(i,j)(i,j)(i,j)都能确认

分析

​ 对于大多数的点我们只要询问(1,1)(1,1)(1,1)(n,1)(n,1)(n,1)即可

代码

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

int main()
{
	ios::sync_with_stdio(0);
	int T;cin>>T;
	while (T--)
	{
		int n,m;
		cin>>n>>m;
		if (n==1&&m==1)cout<<"0\n";
		else if (min(n,m)==1)cout<<"1\n";
		else cout<<"2\n";
	}
}

B. Kalindrome Array

题目描述

​ 给一个数组,问从数组中删除掉一种数字或者不删除能不能构成回文数组

分析

​ 删除掉的那以这种数字一定是我们左右匹配时,遇到的第一对不匹配的两种数字中的一种

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+100;
int a[maxn];
int n;
bool check(int num)
{
	for (int i=1,j=n;i<j;++i,--j)
	{
		while (i<n&&a[i]==num)++i;
		while (j>i&&a[j]==num)--j;
		if (a[i]!=a[j])return false;
	}return true;
}
void solve()
{
	cin>>n;
	for (int i=1;i<=n;++i)cin>>a[i];
	for (int i=1,j=n;i<j;++i,--j)
	{
		if (a[i]!=a[j])
		{
			if (check(a[i])||check(a[j]))
			{
				cout<<"YES\n";
			}else cout<<"NO\n";
			return;
		}
	}cout<<"YES\n";
}
int main()
{
	ios::sync_with_stdio(0);
	int T;cin>>T;
	while (T--)solve();
}

C. Keshi Is Throwing a Party

题目描述

​ 现在要开宴会,我们要邀请尽量多的人

对于一个人iii,他要求邀请的人中比他富有的人不超过a[i]a[i]a[i],比他贫穷的人不超过b[i]b[i]b[i]

我们知道所有人富有值的升序排列

分析

​ 我们可以直接二分答案midmidmid,然后贪心地从穷到富地去选人,维护已经选的人数curcurcur

对于某一个人,如果他之前选的curcurcur不超过b[i]b[i]b[i],然后剩下的mid−cur−1≥a[i]mid-cur-1\ge a[i]midcur1a[i]

那么我们就选择这个人

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+100;
int a[maxn],b[maxn];
int n;
bool check(int m)
{
	int cur = 0;
	for (int i=1;i<=n;++i)
	{
		if (b[i]>=cur&&a[i]>=m-cur-1)
			++cur;
		if (cur==m)return true;
	}
	return false;
}
void solve()
{
	cin>>n;
	for (int i=1;i<=n;++i)cin>>a[i]>>b[i];
	int l = 1,r = n;
	int ans = 1;
	while (l<=r)
	{
		int mid = l+r>>1;
		if (check(mid))
		{
			ans = mid;l = mid+1;
		}else r = mid-1;
	}cout<<ans<<"\n";
}
int main()
{
	ios::sync_with_stdio(0);
	int T;cin>>T;
	while (T--)solve();
}

D. Not Quite Lee

题目描述

​ 给一个长度为nnn的正整数数组aaa ,每一个数代表了一个长为aia_iai的连续数字,具体从哪里开始任意

要求统计,有多少子序列有可能使得每一段连续数字之和的和为000

分析

​ 对于aia_iai,如果他被选中了,那么提供的贡献就是ki×ai+ai×(ai+1)2k_i \times a_i+\frac{a_i \times (a_i +1 )}{2}ki×ai+2ai×(ai+1)

其中kik_iki是首项。

那么,如果我们选择了的序列为[b1,b2,b3…bm][b_1,b_2,b_3\dots b_m][b1,b2,b3bm]

那么实际上就是k1×b1+k2×b2+k3×b3+⋯+km×bm=b1×(b1+1)2+b2×(b2+1)2+b3×(b3+1)2+⋯+bm×(bm+1)2k_1\times b_1 +k_2 \times b_2 +k_3\times b_3 +\dots +k_m\times b_m = \frac{b_1\times (b_1+1)}{2}+\frac{b_2\times (b_2+1)}{2}+\frac{b_3\times (b_3+1)}{2}+\dots +\frac{b_m\times (b_m+1)}{2}k1×b1+k2×b2+k3×b3++km×bm=2b1×(b1+1)+2b2×(b2+1)+2b3×(b3+1)++2bm×(bm+1)

这很容易就让我们想到了贝祖定理,我们去思考gcd(b1,b2,…,bm)gcd(b_1,b_2,\dots ,b_m)gcd(b1,b2,,bm)与右式的整除关系

首先一个结论,如果存在奇数,那么等式一定有解!

因为,若bib_ibi存在奇数,那么gcdgcdgcd一定是奇数。故对于右式,bi×(bi+1)2\frac{b_i\times (b_i+1)}{2}2bi×(bi+1)

  • bib_ibi为偶数,那么bib_ibi一定可以整除gcdgcdgcd,同时,因为gcdgcdgcd为奇数,所以不会消耗bib_ibi222的质因子

    ,因此bib_ibi同时还可以整除222

  • bib_ibi为奇数,那么bib_ibi一定可以整除gcdgcdgcd,同时,bi+1b_i+1bi+1一定可以整除222

我们只要考虑全为偶数的情况了。

gcdgcdgcd的定义是,对于每一个质因子,找到其所有数中的最小指数幂,就是gcdgcdgcd中的指数幂

那么,我们将gcdgcdgcd表示为2t×p2^t\times p2t×p,则ttt是所有bbb222的最小指数幂

我们认为那么对于右式,我们尝试让右式去整除2t×p2^t\times p2t×p

注意到,如果bib_ibi222的指数幂大于ttt的话,那么bi×(bi+1)2\frac{b_i\times (b_i+1)}{2}2bi×(bi+1)一定可以整除

剩下的就是bib_ibi222的指数幂恰巧等于ttt的情况了,要想能够整除,他们必须俩俩合并

因此,222的指数幂为ttt的数一定要是偶数!

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5+100;
const ll mod = 1e9+7;
ll J[maxn],p2[maxn];
ll POW(ll base,ll p)
{
	ll ans=1;
	while (p)
	{
		if (p&1)ans = ans*base%mod;
		base = base*base%mod;
		p>>=1;
	}return ans;
}
ll inv(ll num)
{
	return POW(num,mod-2);
}
ll C(int n,int m)
{
	return J[n]*inv(J[m]*J[n-m]%mod)%mod;
}
int a[maxn],b[50];
int n;
void solve()
{
	cin>>n;
	for (int i=1;i<=n;++i)cin>>a[i];
	for (int i=1;i<=n;++i)if (a[i]%2==0)
	{
		int cnt=0;
		while (a[i]%2==0)
		{
			++cnt;
			a[i]>>=1;
		}b[cnt]++;
	}
	ll ans = (p2[n]+mod-1)%mod;
	int sum = 0;
	for (int i=35;i>=1;sum+=b[i],--i)
		for (int j=1;j<=b[i];j+=2)ans = (ans-C(b[i],j)*p2[sum]%mod+mod)%mod;
	cout<<ans<<endl;
}
int main()
{
	ios::sync_with_stdio(0);
	J[0]=p2[0]=1;
	for (int i=1;i<maxn;++i)J[i]=i*J[i-1]%mod,p2[i]=(p2[i-1]<<1LL)%mod;
	solve();
}

E.AmShZ and G.O.A.T.

题目描述

​ 若一个数组中,元素的平均值是 avgavgavg,比 avgavgavg 小的元素的个数小于比 avgavgavg 大的元素的个数,则称数组是 terrible 的。

​ 删去数组的一些元素,使得该数组的所有子数组都不是 terrible 的,求最少需要删除几个元素?

​ 数据范围:ttt 组数据,1≤t≤1041\leq t\leq 10^41t104,数组长度 2≤n≤2×1052\leq n\leq 2\times 10^52n2×105,数组元素 1≤ai≤1091\leq a_i\leq 10^91ai109,保证对于 1≤i<n1\leq i<n1i<n,有 ai≤ai+1a_i\leq a_{i+1}aiai+1nnn 的和不超过 2×1052\times 10^52×105

分析

​ 考虑最简单的情况,如果三个数字 a,b,c(a<b<c)a,b,c(a<b<c)a,b,c(a<b<c)b−a>c−bb-a>c-bba>cb,则数组是 terrible 的。如果数组中每三个元素都不满足这样的条件,则它不是 terrible 的。

​ 枚举删除后,数组中剩下的最小的元素 xxx

​ 保留所有的 xxx,后面的元素至少为 a1=x+1a_1=x+1a1=x+1,则 a2−a1≥a1−xa_2-a_1\geq a_1-xa2a1a1x,即 a2≥2a1−xa_2\geq 2a_1-xa22a1x,找出最小的符合条件的 a2a_2a2,然后求 a3,a4,⋯a_3,a_4,\cdotsa3,a4,,直到枚举到数组的末尾。

​ 枚举 xxx 的时间复杂度为 O(n)O(n)O(n),但求解 a2,a3,a4,⋯a_2,a_3,a_4,\cdotsa2,a3,a4, 至多只有 O(log⁡109)O(\log 10^9)O(log109) 次,因此可以通过。

代码

#include <bits/stdc++.h>

using namespace std;

int main() {
    int T;
    cin >> T;
    while (T--) {
        int n;
        cin >> n;
        map<int, int> mp;
        for (int i = 1; i <= n; i++) {
            int x;
            scanf("%d", &x);
            mp[x]++;
        }
        int ans = 0;
        for (auto[key, val]: mp) {
            int now = key, s = 1;
            int cnt = val;
            while (true) {
                auto it = mp.lower_bound(now + s);
                if (it == mp.end())
                    break;
                cnt++;
                now = it->first;
                s = it->first - key;
            }
            ans = max(ans, cnt);
        }
        cout << n - ans << endl;
    }
    return 0;
}

F.Mashtali: a Space Oddysey

题目描述

​ 有一个 nnn 个点 mmm 条边的无向图(1≤n≤105,1≤m≤1051\leq n\leq 10^5,1\leq m\leq 10^51n105,1m105),图有可能不是联通的,有可能包含重边,每条边的边权要么是 111 要么是 222。把每条边都改成有向的,使图中 ∣d+(v)−d−(v)∣=1|d^+(v)-d^-(v)|=1d+(v)d(v)=1 的点(好点)的数量尽可能多(d+(v)d^+(v)d+(v) 为向点 vvv 连边的边权和,d−(v)d^-(v)d(v) 为点 vvv 连向其他边的边权和),求调整后最多有多少个好点,并按顺序输出每条边连接的方向。

分析

​ 容易发现一个性质:对于任意一条边,无论是哪个方向,都会对两端的点造成 222444 的影响,因此如果某个点和它相连的边的边权和为偶数,则不可能将这个点调整为好点。

​ 考虑能否构造出一种方案,使得所有边权和为奇数的点都可以成为好点。

​ 将所有的奇度数点都与一个虚点相连一条边权为 111 的虚边。一条边会同时改变两点的度数奇偶情况,即奇度数点变为偶数。所以我们操作之后一定所有点的度数都是偶数,那这个图是一个欧拉回路。

​ 在进行欧拉回路的遍历时,我们首先满足让出边让入边的边权相等,否则使用另一边。

​ 边权和为偶数的可以任意定向,不进行考虑。

​ 边权和为奇数的,有以下两种情况:

  • 奇数条边权为 111 的边,奇数条的边权为 222 的边

    按照我们的操作方法,一定会最后只抵消到只剩一条边权为 111 的和一条边权为 222 的边,该点为好点。

  • 奇数条边权为 111 的边,偶数条的边权为 222 的边

    按照我们的操作方法,最后会抵消到只剩一条边权为 111,该点为好点。

      所以我们只要按照这种操作方法,即可使得所有边权和的奇数的点都是好点。
    

代码

#include <bits/stdc++.h>

using namespace std;
const int N = 600010;

int n, m;

int to[N], from[N], head[N][3];
int fans;
int vis[N], ans[N], cnt[N], dfn[N], sum[N];
vector<int> edge[N][3];

inline void dfs(int u, int now) {
    int fnow = now;
    while (!(edge[u][now].size() <= head[u][now] && edge[u][3 - now].size() <= head[u][3 - now])) {
        dfn[u] = 1;
        while (head[u][now] < edge[u][now].size() && vis[edge[u][now][head[u][now]]])
            head[u][now]++;
        while (head[u][3 - now] < edge[u][3 - now].size() && vis[edge[u][3 - now][head[u][3 - now]]])
            head[u][3 - now]++;
        if (edge[u][now].size() != head[u][now]) {
            ans[edge[u][now][head[u][now]]] = (u == from[edge[u][now][head[u][now]]]) + 1;
            vis[edge[u][now][head[u][now]]] = 1;
            dfs(to[edge[u][now][head[u][now]]] - u, now);
            head[u][now]++;
        } else {
            now = 3 - now;
            if (edge[u][now].size() != head[u][now]) {
                ans[edge[u][now][head[u][now]]] = (u == from[edge[u][now][head[u][now]]]) + 1;
                vis[edge[u][now][head[u][now]]] = 1;
                dfs(to[edge[u][now][head[u][now]]] - u, now);
                head[u][now]++;
            }
        }
        now = fnow;
    }
    return;
}

int main() {
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= m; ++i) {
        int x, y, w;
        scanf("%d %d %d", &x, &y, &w);
        to[i] = x + y;
        cnt[x]++;
        cnt[y]++;
        from[i] = x;
        sum[x] += w;
        sum[y] += w;
        edge[x][w].push_back(i);
        edge[y][w].push_back(i);
    }
    int mcnt = m;
    for (int i = 1; i <= n; ++i) {
        if (sum[i] & 1)
            fans++;
        if (cnt[i] & 1) {
            int x = n + 1;
            int y = i;
            int w = 1;
            to[++mcnt] = x + y;
            from[mcnt] = x;
            edge[x][w].push_back(mcnt);
            edge[y][w].push_back(mcnt);
        }
    }
    for (int i = 1; i <= n + 1; ++i)
        if (!dfn[i])
            dfs(i, 1);
    cout << fans << endl;
    for (int i = 1; i <= m; ++i)
        cout << ans[i];
    cout << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值