Codeforces Round #697 (Div. 3) 题解

A. Odd Divisor

题目大意:给定一个\(n\)问是否存在大于\(1\)的奇因子.

思路

\(2\)直到最后检查即可.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	


int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
		ll n;scanf("%lld",&n);
		while(n % 2 == 0)	n /= 2;
		if(n > 1)	puts("YES");
		else puts("NO");
    }
    return 0;
}

B. New Year's Number

题目大意:给定一个数问是否能由若干个\(2020\)和若干个\(2021\)的和表示出来.

思路

一开始还以为是赛瓦韦斯特定理,形式就是问\(n\)是否能表示成\(2020x+2021y\)\(x,y \geq 1\).就跟赛瓦韦斯特定理没啥关系了,可以直接求出一个\(x = n / 2020\)表示\(n\)中含有多少个\(2020\),还求一个\(n\)\(2020\)的余数,那么现在的变换就可以看做是把某些\(2020\)变成\(2021\),一共有余数个\(2021\)判断是否足够变换即可.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	


int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
		int n;scanf("%d",&n);
		int c = n / 2020,d = n % 2020;
		if(c >= d)	puts("YES");
		else puts("NO");
    }
    return 0;
}

C. Ball in Berland

题目大意:有\(n\)个男的\(m\)个女的,还有\(k\)对关系\((a,b)\)表示第\(a\)个男的可以和第\(b\)个女的跳舞.现在要求你从\(k\)对关系里选出两对不干扰的男女,不干扰即是指同一个人不能出现在两对关系里,问选出两对关系的方案数有多少个.

思路

选出两对,直接枚举一条边\((u,v)\)作为选取的一对人,那么现在的问题就是在所有\(k\)个关系之中,有多少个关系是和\((u,v)\)无关的,也就是在\(k\)跳边之中有多少条边连接的两个点既不是\(u\)也不是\(v\).这个显然可以求一个补集,也就是求与这两个点有关的边有多少个,再从\(k\)个里面去掉就可以了.那么显然也就是两个点的度数之和减一,因为\((u,v)\)这条边会被计入两次.那么枚举答案就可以了.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 2e5+7,M = 2 * N;
int a[N],b[N];
int deg[M];


int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
		int n,m,k;scanf("%d%d%d",&n,&m,&k);
		forn(i,1,n + m)	deg[i] = 0;
		forn(i,1,k)	scanf("%d",&a[i]),++deg[a[i]];
		forn(i,1,k)	scanf("%d",&b[i]),++deg[b[i] + n];
    	ll res = 0;
    	forn(i,1,k)
    	{
    		// cout << deg[a[i]] << " " << deg[b[i] + n] << endl;
    		int sum = deg[a[i]] + deg[b[i] + n] - 1;
    		res += k - sum;
    	}
    	printf("%lld\n",res / 2);
    }
    return 0;
}

D. Cleaning the Phone

题目大意:有\(n\)个垃圾,每个垃圾体积是\(a_i\),同时每个垃圾根据自己的处理难度需要花费\(b_i\)元.问至少丢掉\(m\)体积的垃圾,最少需要花多少钱.

在本题中,\(b_i\)仅有两种取值\(1/2\).

思路

因为\(b_i\)只有两种取值,所以第一步可以先分组,把所有花费为\(1\)的放在一起,花费\(2\)的同理.那么一个比较显然的方向就是直接把两个数组都排序,排序完了之后贪心的选择两边的策略.不过很显然贪心是无法处理很多特殊情况的,需要不停地打补丁,不考虑直接贪心.

另外一个想法就是直接枚举,当然不能枚举两边选多少,只能枚举一边,简单起见枚举花费\(2\)的垃圾选了\(i\)件,选择是从体积大的往体积小的枚举,那么可以维护一个当前花费\(2\)的垃圾总共有多少体积,剩下在\(1\)的垃圾中至少要选出\(m-cur\)体积的垃圾,这一步可以先给花费\(1\)的数组从大到小排序,排序之后直接二分找到第一个位置\(\geq m - cur\)的位置,那么这个位置就是需要选出的个数,直接更新答案就可以了.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 2e5+7;
int a[N],b[N];

int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
		int n,m;scanf("%d%d",&n,&m);
		forn(i,1,n)	scanf("%d",&a[i]);
		forn(i,1,n)	scanf("%d",&b[i]);
		vector<int> a1,a2;
		forn(i,1,n)	if(b[i] == 1)	a1.push_back(a[i]);else a2.push_back(a[i]);
		sort(a1.begin(),a1.end());reverse(a1.begin(),a1.end());
		sort(a2.begin(),a2.end());reverse(a2.begin(),a2.end());
		vector<ll> sum;sum.push_back(0);for(auto& v : a1)	sum.push_back(sum.back() + v);
		ll cur = 0;int res = 1e9;
		{
			auto iter = lower_bound(sum.begin(),sum.end(),m - cur);
			if(iter != sum.end())	res = min(res,(int)(iter - sum.begin()));
		}
		for(int i = 0;i < a2.size();++i)
		{
			cur += a2[i];
			auto iter = lower_bound(sum.begin(),sum.end(),m - cur);
			if(iter == sum.end())	continue;
			res = min(res,(i + 1) * 2 + (int)(iter - sum.begin()));
		}
		printf("%d\n",res == 1e9 ? -1 : res);
		
    }
    return 0;
}

E. Advertising Agency

题目大意:现在要卖一个商品,有\(n\)个吹牛逼的人,每个吹牛逼的人有\(a_i\)个粉丝,假设每个人的粉丝集合都没有重合的地方.现在想要雇佣\(k\)个人上微博吹牛逼,问在选出的\(k\)个人的粉丝总和数量最大的前提之下,有多少种选择方案.

思路

傻逼题.

值得选择的部分只有最后一个人,举个例子来说就是\(1,1,2,3,4\)如果要选出\(4\)个人,那么首先排序,要让和最大肯定从大的往小的选,那么除了\(1\)这个最后一个数之外的数都没有任何选择的余地,只有最后一个数可能会有选择的余地,只需要找出整个序列中有多少个\(1\)以及被\(k\)个数选入的有多少个,那么答案就是在整个序列中有多少个中选出被选入的个数,组合数即可.

由于数据范围小的离谱,,,这题直接用杨辉三角递推组合数即可,不用乘法逆元.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 1005,MOD = 1e9+7;
int a[N],C[N][N];

void init()
{
	for(int i = 0;i < N;++i)
	{
		for(int j = 0;j <= i;++j)
		{
			if(!j)	C[i][j] = 1;
			else C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % MOD;
		}
	}
}

int main()
{
	init();
    int T;scanf("%d",&T);
    while(T--)
    {
		int n,k;scanf("%d%d",&n,&k);
		forn(i,1,n)	scanf("%d",&a[i]);
    	sort(a + 1,a + n + 1);
    	int cnt = 0,sz = 0,target = a[n - k + 1];
    	forn(i,1,n)
    	{
    		if(a[i] == target)
    		{
    			++cnt;
    			if(i >= n - k + 1)	++sz;
    		}
    	}
    	// cout << cnt << " " << sz << endl;
    	printf("%d\n",C[cnt][sz]);
    }
    return 0;
}

F. Unusual Matrix

题目大意:给定一个\(n*n\)的矩阵,元素只有\(0/1\).每次可以选出矩阵中的一行或者一列全部异或1.问若干次操作之后矩阵\(a\)是否能变成\(b\).

思路

老套路题了.

首先注意到所有的操作,第一个如果对同一行或者同一列操作两次是毫无意义的,第二个操作的先后执行顺序也是毫无意义的.所以我们不妨把无序的操作方案变成有规律的:先把所有的方案分成两种:一种是执行了第一行反转的,另外一种是没有执行第一行反转的.

如果把所有方案如上分成两种,那么接下来可以注意到一个结论:第一行的某个元素如果与\(b\)中某个元素不对应,那么他一定需要反转,但是把所有方案如上分开的意义就在于,接下来不能使用反转第一行的操作了,也就是说现在如果跟目标矩阵某个元素不相同,只能通过反转一个列的操作使之相等.在做完了第一行之后,同样根据一开始的结论,接下来任何列,如果再操作一次,就是毫无意义的,进而现在如果有某一行元素不相同,那么只能动用行操作,继续往下推,如果发现某行第一个元素不同就反转这一行.到最后检查整个矩阵是否是和目标矩阵相同的就就可以了.

当然由于方案分割成了两个大的集合,先做一次再把第一行完全反转再做一次就行了.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 1005;
char sa[N][N],sb[N][N];
int a[N][N],b[N][N];
int n;

bool check(vector<vector<int>> a,vector<vector<int>> b)
{
	forn(j,1,n)
	{
		if(a[1][j] != b[1][j])
			forn(i,1,n)	a[i][j] ^= 1;
	}
	forn(i,1,n)
	{
		if(a[i][1] != b[i][1])
			forn(j,1,n)	a[i][j] ^= 1;
	}
	forn(i,1,n)	forn(j,1,n)	if(a[i][j] != b[i][j])	return 0;
	return 1;	
}

int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
		scanf("%d",&n);
		forn(i,1,n)	scanf("%s",sa[i] + 1);
		forn(i,1,n)	scanf("%s",sb[i] + 1);
    	forn(i,1,n)	forn(j,1,n)	a[i][j] = (sa[i][j] - '0');
    	forn(i,1,n)	forn(j,1,n)	b[i][j] = (sb[i][j] - '0');
    	
    	vector<vector<int>> a_(n + 17,vector<int>(n + 17)),b_(n + 17,vector<int>(n + 17));
    	
    	forn(i,1,n)	forn(j,1,n)	a_[i][j] = a[i][j];
    	forn(i,1,n)	forn(j,1,n)	b_[i][j] = b[i][j];
    	
    	
    	if(check(a_,b_))
    	{
    		puts("YES");
    		continue;
    	}
    	forn(i,1,n)	a_[1][i] ^= 1;
    	if(check(a_,b_))	puts("YES");
    	else puts("NO");
    }
    return 0;
}

G. Strange Beauty

题目大意:定义一个序列\(a\)是牛逼的,当且仅当对于任意一对\(i,j\)\(i\neq j\)的前提下如果有\(a_i | a_j\)或者\(a_j | a_i\).现在给定一个序列\(a\),问最少删掉几个数可以使整个序列变成牛逼的.

思路

还是一样的,先把所有数排个序,这样的话牛逼的序列条件就可以等价的变成所有较小的数都是较大的数的因子,这样的话可以只考虑一边的关系,本来的序列要考虑大的到小的小的到大的不好做.

剩下的可以直接\(dp\),考虑\(f[i]\)表示\(i\)作为整个序列里面最大的数的时候,这个序列最多包含多少个数,转移就是\(f[i] = cnt(i) + \max\{f[y]\}\)式中\(y\)\(i\)的因子,且\(y < i\).\(cnt(i)\)表示原来的序列里面\(i\)的个数.这个\(dp\)的依据就是划分最后一个数.

由于这个题时限放到了5s.所以可以暴力做这个题:直接对于每个数根号分解他的因数.当然还有一个\(logn\)调和级数的做法,也就是枚举每个数\(x\)有哪些数\(y\)是包含\(x\)作为因子的,直接把\(x\)的贡献算进去就可以了.太简单了也没什么好说的.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 2e5+7;
int a[N],cnt[N],f[N];

int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
		int n;scanf("%d",&n);
		int maxv = 0;
		forn(i,1,n)	scanf("%d",&a[i]),maxv = max(maxv,a[i]);
		forn(i,1,maxv)	cnt[i] = 0,f[i] = 0;
		forn(i,1,n)	++cnt[a[i]];
		
		forn(x,1,maxv)
		{
			if(!cnt[x])	continue;
			f[x] += cnt[x];
			for(int y = 2 * x;y <= maxv;y += x)
				f[y] = max(f[y],f[x]);
		}
		
		int res = 0;
		forn(i,1,maxv)	res = max(res,f[i]);
		printf("%d\n",n - res);
    }
    return 0;
}
posted @ 2021-01-26 10:25  随处可见的阿宅  阅读(411)  评论(0编辑  收藏  举报