CF Round 86

A. Road To Zero

题意: 给定 x 和 y ,a 和 b ,有两种操作

①:花费 a ,选择 x 或 y 加一或者减一;

②:花费 b ,同时给 x 和 y 加一或者减一;

问最少需要花费多少可以使 x y 都为 0;

分析: 首先 x 和 y 之间的差值肯定是必须由 ① 操作补平的,然后考虑 x=y 的时候,是连续两次 ① 操作把 x 和 y 的值向 0 靠近 一个单位还是一次 ② 操作,比较一下就可以了,所以是简单的贪心;

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
	int t; cin>>t;
	while(t--)
	{
		ll a,b,x,y; cin>>x>>y>>a>>b;
		ll ans=abs(x-y)*a;
		ans+=min(min(x,y)*b,min(x,y)*2*a);
		cout<<ans<<endl;
	}
}

B. Binary Period

题意: 给定一个仅由 0 和 1 组成的序列 t ,现需要你构造一个序列 s 满足一下条件

①:s 仅由 0 和 1 组成;

②:s 的长度不超过 t 的两倍,即 |s|<=2*|t|;

③:t 是 s 的子序列;

④:在满足条件①-③的情况下令 s 的循环节最小;

分析: 如果 t 仅包含 0 或 1,那么 t 就是满足上述四个条件的答案序列,显然它的最小循环节长度是 1;否则,只需要在 t 中找到相邻相同的位置然后中间插入不一样的,最后使序列变成 101010... 或者 010101.. 的形式就可以了,它的最小循环节是 2;

代码:

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

int main()
{
	int t; cin>>t;
	while(t--)
	{
		string s; cin>>s;
		int n=s.length();
	    int z=0,o=0;
	    for(int i=0;i<n;i++) if(s[i]=='0') z++;else o++;
	    if(!z||!o) cout<<s<<endl;
	    else
	    {
	        cout<<s[0];
	        int pre=s[0]-'0';
	        for(int i=1;i<n;i++)
	          if((s[i]-'0')!=pre)
	            cout<<s[i],pre^=1;
	          else
	            cout<<(pre^1)<<s[i];
            cout<<endl;
		}
	}
} 

C. Yet Another Counting Problem

题意: 给定 a 和 b ,求区间 [l,r] 内有多少数满足

\[((x~mod~a)~mod~b)\neq((x~mod~b)~mod~a) \]

分析: 先把 x 的形式变一变

\[x=lcm(a,b)*y+k~~~~~~~(k<lcm(a,b)) \]

对于 lcm(a,b)*k 部分,不论先模 a 还是 b 最后都是 0 ,所以对结果有影响的部分是 k,这样范围就缩小到了 [1,lcm(a,b)] ,接下来遍历统计区间 [1,lcm(a,b)] 内的情况就可以了;

代码:

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

int s[200*200+19];

ll gcd(ll a,ll b){
	if(a==0) return b;
	if(b%a==0) return a;
	return gcd(b%a,a);
}

ll lcm(ll a,ll b){
	return a/gcd(a,b)*b;
}

ll l[600],r[600];

int main()
{
	ios::sync_with_stdio(false);
	int t;cin>>t;
	while(t--)
	{
		memset(s,0,sizeof(s));
		ll a,b,q; cin>>a>>b>>q;
		ll L=lcm(a,b),cnt=0;
		for(ll i=1;i<=L;i++)
		{
			if(((i%a)%b)!=((i%b)%a)) s[i]=1,cnt++;  //统计区间[1,lcm(a,b)]的情况
		}
		for(int i=2;i<=L;i++) s[i]+=s[i-1];   //做一下前缀和
		
		for(int i=1;i<=q;i++) cin>>l[i]>>r[i],l[i]--; 
		
		for(int i=1;i<=q;i++)
		{
			ll A=r[i]/L*cnt+s[r[i]%L];
			ll B=l[i]/L*cnt+s[l[i]%L];
			cout<<A-B;
			if(i==q) cout<<endl;
			else cout<<' '; 
		} 
	}
} 

D. Multiple Testcases

题意: 给定长度为 n 的 m 数组 ,然后给定长度为 k 的 c 数组,现在给 m 数组内的元素分组,要求组数尽量少的前提下满足每组的元素 ( \(m_i<=k\) )

①:大于 1 的元素不超过 \(c_1\) 个;

②:大于 2 的元素不超过 \(c_2\) 个;

...

③:大于 k 的元素不超过 \(c_k\) 个;

求具体分组方案,(\(n \geq c_1 \geq c_2 \geq ... \geq c_k \geq 1\))

分析: c 数组是非递增的,我们把 m 数组内的元素由大到小进行分组就可以了;

代码:

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

const int N = 2E5+10;

vector<int>ans[N];
int cnt=0;

int n,k,m[N],c[N];

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin>>n>>k;
	for(int i=1,x;i<=n;i++) cin>>x,m[x]++;
	for(int i=1;i<=k;i++) cin>>c[i];
	
	int l,r=n,sum=0,H=1;
	for(int i=k;i>=1;i--)
	{
		if(i!=k&&c[i]>c[i+1]) H=1;  //c[i]>c[i+1],即前面的组还可以再分配元素,下标H初始化
		while(m[i]--)
		{
			while(ans[H].size()==c[i]) H++;
			ans[H].push_back(i); 
		}
		cnt=max(cnt,H);
	}
	cout<<cnt<<'\n';
	for(int i=1;i<=cnt;i++)
	{
		int n=ans[i].size();
		cout<<n;
		for(auto s:ans[i]) cout<<' '<<s;
		cout<<'\n';
	}	
} 

E. Placing Rooks

题意: 让你在一个 \(n \times n\) 的棋盘内放置 n 颗石子,最后的棋局需要满足

①:棋盘上所有的点都被石子的攻击范围覆盖 (一个石子的攻击范围是它所在位置的那一行与那一列);

②:有 k 对 石子互相攻击 (互相攻击的定义是两颗石子属同一行或同一列,且之间无其它石子);

输出满足上述两个条件的棋局的方案总数 (mol 998244353)

分析: 若要满足条件①,那么每一行(或者每一列)都至少得有一颗石子,即一行(或一列)一颗。而行列的情况是对称的,所以仅考虑每一行摆一颗石子的方案,最后对结果乘以2就可以了( k=0 除外,因为 k=0 等价于每列每行同时只摆一颗石子,所以不用乘以2)

然后,需要正好 k 对石子互相攻击,一列中如果有 m 颗石子,那么会有 m-1 对石子相互攻击,所以至多只能满足 n-1 对石子相互攻击(即所有石子放在同一列),那么显然 k>n-1 的情况无解;接下来,我们设 n 颗石子一共摆了 x 列(每列至少一颗),那么一共有 n-x 对石子互相攻击(每列是它的石子数-1),所以只需要把 n 颗石子都放在 n-k 列中就可以了。列数的方案一共有 C(n,n-k) 种,可是具体的摆放方案还是很复杂,这里就可以用容斥原理来解决:

首先,选择完确定的 n-k 列之后,没有任何限制条件,一共有 \((n-k)^n\) 种摆放方案,然后减去至少有一列为空的方案,即 \(C(n-k,1) \times (n-k-1)^n\) ,再加上至少有两列为空的方案,即 \(C(n-k,2)\ \times (n-k-2)^n\) ...

\[ans=\sum_{i=0}^{n-k-1}(-1)^i\times C(n-k,i)\times(n-k-i)^n \]

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 998244353;
const int N = 2E5+10;
ll qpow(ll a,ll n)
{
	ll res=1;
	while(n)
	{
		if(n&1) res=res*a%MOD;
		a=a*a%MOD;
		n>>=1;
	}
	return res;
}

ll fac[N];

ll C_mod(ll n,ll m)
{
	ll fm,fz;
	fm=fac[m]*fac[n-m]%MOD;
	fz=fac[n];
	return fz*qpow(fm,MOD-2)%MOD; 
}

//Lucas定理,组合数取模
ll Lucas(ll n,ll m)
{
	if(!m) return 1;
	return C_mod(n%MOD,m%MOD)*Lucas(n/MOD,m/MOD)%MOD;
}

int main()
{
	fac[0]=fac[1]=1;
	for(int i=2;i<N;i++) fac[i]=fac[i-1]*i%MOD; //预处理阶乘
	
	ll n,k; cin>>n>>k;
	if(k>=n) cout<<"0",exit(0);  //无解的情况
	ll m=n-k;
	
	ll ANS=0;
	for(int i=0;i<m;i++)
	{
		ll res=qpow(m-i,n)*Lucas(m,i)%MOD;
		if(i%2) ANS=(ANS-res+MOD)%MOD;
		else  ANS=(ANS+res)%MOD;
	} 
	ANS=(ANS*Lucas(n,m))%MOD;
	if(k) ANS=ANS*2%MOD;  
	cout<<ANS;
} 

F. Make It Ascending

题意: 给定一个长度为 n 的数组 a (n<=15),你可以进行若干次如下操作

  • 选择 i 和 j ,a[j]+=a[i] 并且删去 a[i] ;(删去之后,i 之后的元素下标都往前移一位)

求最少操作次数,使得最后的数组 a 严格单调递增,并给出具体操作方案;

分析: 我们先不考虑删除操作,问题相当于将原数组 a 内的元素分成若干组,每一组累加所有的元素和放到其中一个元素原来的位置,其它位置无效,最后所有有效位置的数从左到右严格单调递增,那么我们从左到右(即从小到大)枚举最终的数组,n <= 15 ,所以可以用状压,设 dp[i][j][z] 表示枚举到第 i 组,前 i 组的状态集合 j,最右边(即最大的)的数放置的位置为 z 的情况下第 i 组的最小值,那么

  1. 第 i+1 组的状态肯定是从 \(j^(1<<n)-1\) 中枚举而来;
  2. 第 i+1 组的 z 值肯定是大于第 i 组的;
for(int i=0;i<=n;i++)
   for(int j=0;j<(1<<n);j++)
      for(int z=0;z<=n;z++)
	     dp[i][j][z]=INF;   //初始化
	      
dp[0][0][0]=0;
for(int i=1;i<=n;i++)    //第 i 组
   for(int j=0;j<(1<<n);j++)    //前一组的状态
      for(int z=0;z<n;z++)      //前一组最右边的数所在的位置
      {
         if(dp[i-1][j][z]==INF) continue;
          
         int m=j^((1<<n)-1);    //当前组的集合全集
         for(int k=m;k;k=(k-1)&m)
         {
        		if(sum[k]<=dp[i-1][j][z]) continue;
        		if((k>>z)==0) continue;  //第i+1组的z值肯定要大于第i组
				
				int nz=z+__builtin_ctz(k>>z)+1;
				if(dp[i][j|k][nz]>sum[k])
				{
					dp[i][j|k][nz]=sum[k];
					 p[i][j|k][nz]=P(j,z);   //记录路径
				} 
		 }
	  }

完整代码

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

#define fi first
#define se second

typedef pair<int,int> P;
const int N = 15; 
const int INF = 1E9+7;

int dp[N+1][1<<N][N+1];
P    p[N+1][1<<N][N+1];
int n,a[N],sum[1<<N];

void work()
{
	cin>>n;
	for(int i=0;i<n;i++) cin>>a[i];
	for(int i=0;i<(1<<n);i++)
	{
		sum[i]=0;
		for(int j=0;j<n;j++) if((1<<j)&i) sum[i]+=a[j]; 
 	}
	
	for(int i=0;i<=n;i++)
	  for(int j=0;j<(1<<n);j++)
	    for(int z=0;z<=n;z++)
	      dp[i][j][z]=INF;
	      
	dp[0][0][0]=0;
    for(int i=1;i<=n;i++)
      for(int j=0;j<(1<<n);j++)
        for(int z=0;z<n;z++)
        {
        	if(dp[i-1][j][z]==INF) continue;
        	
        	int m=j^((1<<n)-1);
        	for(int k=m;k;k=(k-1)&m)
        	{
        		if(sum[k]<=dp[i-1][j][z]) continue;
        		if((k>>z)==0) continue;
				
				int nz=z+__builtin_ctz(k>>z)+1;
				if(dp[i][j|k][nz]>sum[k])
				{
					dp[i][j|k][nz]=sum[k];
					 p[i][j|k][nz]=P(j,z);
				} 
			}
		}
	
    //找到最优解
	int a=-1,c=-1;
	for(int i=n;i>=0;i--)
	{ 
	  for(int z=n;z>=0;z--)
	    if(dp[i][(1<<n)-1][z]<INF){
	    	c=z; break;
		}
	  if(c!=-1){
	  	a=i;break;
	  }
    }
    
    
    vector<P>ans;
    int b=(1<<n)-1;
    for(int i=a;i>0;i--)
    {
    	int nb=p[i][b][c].fi;
    	int nc=p[i][b][c].se;
    	
    	int m = nb^b;
    	
        for(int j=0;j<n;j++)
		    if(m&(1<<j)&&j!=c-1) 
			    ans.push_back(P(j,c-1));
		
		b=nb,c=nc; 
	}
    
    cout<<(int)ans.size()<<'\n';
    for(int i=0;i<ans.size();i++)
    {
    	int x=ans[i].fi;
    	int y=ans[i].se;
    	
    	for(int j=0;j<i;j++) if(ans[j].fi<ans[i].fi) x--;
    	for(int j=0;j<i;j++) if(ans[j].fi<ans[i].se) y--;
    	cout<<x+1<<' '<<y+1<<'\n';
	}
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int T; cin>>T;
	while(T--) work();
} 
posted @ 2020-05-04 20:02  Joker&Liar  阅读(184)  评论(0编辑  收藏  举报