Educational Codeforces Round 86 (Rated for Div. 2) Apr/26/2020 22:35UTC+8

比赛链接 https://codeforces.com/contest/1342
比赛记录 https://blog.csdn.net/cheng__yu_/article/details/105395197

A. Road To Zero

在这里插入图片描述
题意:给定两个非负整数x、y,可以花费a元让x或者y减-1,或者花费b元让x和y同时减-1。问x=y=0时的最小花费。
思路:a的效果是1,b的效果是2,。所以2倍的a和b是等效的,如果2a>b,说明b的性价比更高。否则就只使用a

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,maxm=1e5+5;
const int mod=1e9+7,inf=0x7f7f7f7f;

int t;
ll x,y,a,b; 
 
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%lld%lld",&x,&y);
		scanf("%lld%lld",&a,&b);
		if(x>y)
			swap(x,y);
		ll ans=0;
		if(2*a<=b)
			ans=(x+y)*a;	
		else
			ans=(y-x)*a+x*b;
		printf("%lld\n",ans);
	}
	return 0;
}

B. Binary Period(找最小周期)

在这里插入图片描述

题意:给定一个01串 t , 在 t 中插入一下字符0或1,使它的周期为k且k是它的最小周期
思路:如果 t 中字母全相同,周期是1。如果不相同就可以构造成010101,那么周期是2

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,maxm=1e5+5;
const int mod=1e9+7,inf=0x7f7f7f7f;

int t;

int main()
{
	scanf("%d",&t);
	while(t--)
	{
		string s;
		cin>>s;
		
		char c=s[0];
		bool valid=true;
		for(int i=1;i<s.length();++i)
		{
			if(s[i]==s[0])
				continue;
			valid=false;
			break;
		}
		if(valid)
		{
			cout<<s<<"\n";
			continue;
		}
		int n=s.length();
		string ans="";
		for(int i=1;i<=n;++i)
			ans+="01";
		cout<<ans<<"\n";
	}
	return 0;
}

C. Yet Another Counting Problem(数学找周期)

在这里插入图片描述
题意:找给定区间 [ l , r ] [l,r] [l,r] x % a % b ! = x % a % b x \% a \% b != x \% a \% b x%a%b!=x%a%b的个数。
在这里插入图片描述

思路一

  • 找不相等的,就去找相等的
  • 找区间 [ l , r ] [l,r] [l,r] 就找 [ 1 , n ] [1,n] [1,n] 就可以了,后面可以作差
  • 可以假设 a < b a<b a<b ,这样就相当于 找 x mod a == x mod b mod a,好像没什么用。
  • 然后我就打了个表,发现 1 到 b - 1 都是相同的。然后一个LCM内有b个,是一个周期
  • 那么[1,n]中的答案就是: n / l c m ( a , b ) + m i n ( b − 1 , n % l c m ( a , b ) ) n/lcm(a,b) + min(b-1,n\%lcm(a,b) ) n/lcm(a,b)+min(b1,n%lcm(a,b))

思路二

  • 看到 x % a % b ! = x % a % b x \% a \% b != x \% a \% b x%a%b!=x%a%b,就应该想到 (x + ab) %a %b != (x + ab) %b %a。简单的说,就是看到这个式子的周期是 ab,所有数的结果都在 [0,ab-1]中循环
  • 又因为 a、b都很小(200),所以可以在[0,ab-1]范围内统计一个f[i],表示[1,i]符合条件的个数是多少。
  • 这样求n的时候,答案就是: n / a b × f [ a b − 1 ] + f [ n % a b ] n/ab\times f[ab-1]+f[n\%ab] n/ab×f[ab1]+f[n%ab]

总结

  • 两种方法都好,自己比赛中用的第一种,第二种似乎更胜一筹。
  • 要是能够马上看出周期性,这题目也就可以秒了
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,maxm=1e5+5;
const int mod=1e9+7,inf=0x7f7f7f7f;

ll t,a,b,q;
ll l,r,LCM;

int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;
}

int calc(ll n)
{
    int ans=n/LCM*b+min(b-1,n%LCM);
    return ans;
}

int main()
{
    cin>>t;
    while(t--)
    {
        cin>>a>>b>>q;
        if(a>b)
            swap(a,b);
        LCM=a/gcd(a,b)*b;
        vector<int> ans;
        while(q--)
        {
            cin>>l>>r;
            ll res=calc(r)-calc(l-1);
            res=r-l+1-res;
            ans.push_back(res);
        }
        for(auto i : ans)
            cout<<i<<" ";
        cout<<"\n";

    }
	return 0;
}

D. Multiple Testcases(贪心)

在这里插入图片描述

题意:给定一个长度为n的数组m,和长度为k的数组c。希望把数组m划分成尽可能少的数组,满足每个数组中 ≥ 1 \ge 1 1 的数字个数不超过 c 1 c_1 c1,数组中 ≥ 2 \ge 2 2的数字个数不超过 c 2 c_2 c2,数组中 ≥ 3 \ge 3 3的数字个数不超过 c 3 c_3 c3
思路:题目一读完,思路就有了。

  • 统计一下m数组中, ≥ 1 \ge 1 1 的数字有多少个, ≥ 2 \ge 2 2的数字有多少个,放到counts数组中
  • 统计完后,计算 c n t = c o u n t s [ i ] / c [ i ] + ( c o u n t s [ i ] % c [ i ] ? 1 : 0 ) cnt=counts[i]/c[i] + (counts[i]\%c[i]?1:0) cnt=counts[i]/c[i]+(counts[i]%c[i]?1:0)取一个最大值
  • 最后直接按顺序填到cnt个数组中就好了
  • PS:比赛的时候,还在想着要怎么贪心地填才好,实际上直接填就好了
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5,maxm=1e5+5;
const int mod=1e9+7,inf=0x7f7f7f7f;

int n,k;
int a[maxn],c[maxn];
int suff[maxn],counts[maxn];

int main()
{
    cin>>n>>k;
    for(int i=1;i<=n;++i)
        cin>>a[i],counts[a[i]]++;

    suff[k+1]=0;
    for(int i=k;i>=1;--i)
        suff[i]=counts[i]+suff[i+1];

    for(int i=1;i<=k;++i)
        cin>>c[i];

    int cnt=0;
    for(int i=1;i<=k;++i)
    {
        int tmp=suff[i]/c[i]+(suff[i]%c[i]?1:0);
        cnt=max(cnt,tmp);
    }

    vector<int> ans[maxn];

    int p=1;
    for(int i=1;i<=k;i++)
    {
        while(counts[i])
        {
            ans[p].push_back(i);
            counts[i]--;
            p++;
            if(p==cnt+1)
                p=1;
        }
    }

    cout<<cnt<<"\n";
    for(int i=1;i<=cnt;++i)
    {
    	cout<<ans[i].size()<<" "; 
        for(auto j : ans[i])
            cout<<j<<" ";
        puts("");
    }
	return 0;
}

E. Placing Rooks(容斥)

在这里插入图片描述

题意:在一个 n × n n\times n n×n 的棋盘上放置n个车,要求恰好有k个车能够相互攻击,并且棋盘上的每一个空位都在攻击范围内

思路:每一行或者每一列都放一个车。假设每行放一个,那么当 x x x 个车在同一列时,就能形成 x − 1 x-1 x1 对相互攻击的车。其实可以这样想,一开始是每行每列都有一个车,每次从某一列拿掉一个车,放在另一列上时,就会形成一对相互攻击的车。

  • 因此答案就是:先选择n-k行,然后将这 n n n个车放在这 n − k n-k nk行上的方案数
    C n n − k F ( n , n − k ) C_{n}^{n-k}F(n,n-k) CnnkF(n,nk)

  • F(n,m)是将n个不同的球放在m个不同的盒子中,无空盒的方案数。可以用容斥来写。

∣ A 1 ‾ ∩ A 2 ‾ ∩ ⋯ ∩ A m ‾ ∣ = ∣ S ∣ − ∑ ∣ A i ∣ + ∑ ∣ A i ∩ A j ∣ + ⋯ + ( − 1 ) m ∑ ∣ A 1 ∩ A 2 ∩ ⋯ ∩ A m ∣ |\overline{A_1} \cap \overline{A_2}\cap \dots \cap \overline{A_m} |=|S|-\sum{|A_i|}+\sum{|A_i \cap A_j|} \\ +\dots +(-1)^m\sum{|A_1 \cap A_2\cap \dots \cap A_m|} A1A2Am=SAi+AiAj++(1)mA1A2Am

  • 这里,性质 P 1 P_1 P1 就是第一个盒子空出来, ∣ A 1 ∣ |A_1| A1 就是第一个盒子为空的方案数
    性质 P 2 P_2 P2 就是第二个盒子空出来, ∣ A 2 ∣ |A_2| A2 就是第二个盒子为空的方案数
    ∣ A i ∣ = ( m − 1 ) n |A_i|=(m-1)^n Ai=(m1)n ∣ A i ∩ A j ∣ = ( m − 2 ) n |A_i \cap A_j|=(m-2)^n AiAj=(m2)n
  • 这里的计算只与空出来的盒子数量有关,而与具体是哪个盒子无关。
    ∣ A 1 ‾ ∩ A 2 ‾ ∩ ⋯ ∩ A m ‾ ∣ = C m 0 ∣ S ∣ − C m 1 ∣ A i ∣ + C m 2 ∣ A i ∩ A j ∣ + ⋯ + ( − 1 ) m C m m ∣ A 1 ∩ A 2 ∩ ⋯ ∩ A m ∣ = ∑ i = 0 m ( − 1 ) m C m i ( m − i ) n |\overline{A_1} \cap \overline{A_2}\cap \dots \cap \overline{A_m} |=C_m^0|S|-C_{m}^1{|A_i|}+C_{m}^2{|A_i \cap A_j|} \\ +\dots +(-1)^mC_m^m{|A_1 \cap A_2\cap \dots \cap A_m|}\\ =\sum_{i=0}^m(-1)^mC_m^i(m-i)^n A1A2Am=Cm0SCm1Ai+Cm2AiAj++(1)mCmmA1A2Am=i=0m(1)mCmi(mi)n
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,maxm=1e5+5;
const int mod=998244353,inf=0x7f7f7f7f;

const int N=2e5;
int finv[N+10],fac[N+10];

int qpow(int base,int n,int mod)
{
	int res=1;
	while(n)
	{
		if(n&1)
			res=1ll*res*base%mod;
		base=1ll*base*base%mod;
		n>>=1;
	}
	return res;
}

int add(int &a,int b)
{
	if(a+b>mod)
		a=a+b-mod;
	else
		a=a+b;
	if(a<0)
		a+=mod;
}

void init()
{
	fac[0]=fac[1]=1;
	for(int i=2;i<=N;++i)
		fac[i]=1ll*fac[i-1]*i%mod;
	finv[N]=qpow(fac[N],mod-2,mod);
	for(int i=N-1;i>=0;--i)
		finv[i]=1ll*finv[i+1]*(i+1)%mod;
}

int C(int n,int m)
{
	return 1ll*fac[n]*finv[m]%mod*finv[n-m]%mod;
}

int F(int n,int m)
{
	int ans=0;
	ll x=1;
	for(int i=0;i<=m;++i)
	{
		add(ans,x*C(m,i)*qpow(m-i,n,mod)%mod);
		x=-x;
	}
	return ans;
}

ll n,k;

int main()
{
	init();
	scanf("%lld%lld",&n,&k);
	int ans;
	if(k==0)
		ans=fac[n];
	else if(k>=n)
		ans=0;
	else
		ans=2ll*C(n,n-k)%mod*F(n,n-k)%mod;
	printf("%d\n",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值