zoukankan      html  css  js  c++  java
  • 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) eq((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<<'
    ';
    	for(int i=1;i<=cnt;i++)
    	{
    		int n=ans[i].size();
    		cout<<n;
    		for(auto s:ans[i]) cout<<' '<<s;
    		cout<<'
    ';
    	}	
    } 
    

    E. Placing Rooks

    题意: 让你在一个 (n imes 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) imes (n-k-1)^n) ,再加上至少有两列为空的方案,即 (C(n-k,2) imes (n-k-2)^n) ...

    [ans=sum_{i=0}^{n-k-1}(-1)^i imes C(n-k,i) imes(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()<<'
    ';
        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<<'
    ';
    	}
    }
    
    int main()
    {
    	ios::sync_with_stdio(false);
    	cin.tie(nullptr);
    	int T; cin>>T;
    	while(T--) work();
    } 
    
  • 相关阅读:
    SQL语句的执行顺序
    凭兴趣求职80%会失败,为什么
    Google Analytics:为链接点击设定事件追踪的方法
    org/hamcrest/SelfDescribing
    Idea使用记录--添加Problems&&解决Autowired报错could not autowire
    RBAC(Role-Based Access Control)基于角色的访问控制
    如何在不同编程语言中获取现在的Unix时间戳(Unix timestamp)?
    记录使用Hibernate查询bean中字段和数据库列类型不匹配问题
    Java添加自定义注解
    JS获取select的值
  • 原文地址:https://www.cnblogs.com/17134h/p/12827978.html
Copyright © 2011-2022 走看看