AtCoder Beginner Contest 262(D-F)

D - I Hate Non-integer Number

题意:一个长度为n的数组,选择其中的 x项,问其中有多少种选择,这x项的和可以被选择的数目整除,比如,选择3个数,和为6,那么6/3=2,就可以被整除。

题解: 每个数有选与不选两种可能,dp,观察数据,1<=a[i]<=1e9,所以数组中不能存储总和,会爆,而两项之间其实有关系的是被求余之后的数,所以存储余数即可。

dp[i][j][k]表示的是前i个数中,选择j个,然后余数为k的个数,所以 dp[i][j][(k+a[i])%z]+=dp[i-1][j-1][k];

 

复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pll;
const int N=2e5+5;
const ll inf=1e18;
const ll mod=998244353;
ll dp[105][105][105];
signed main(){
   ios::sync_with_stdio(false);
   cin.tie(0);cout.tie(0);
  ll n;cin>>n;
  vector<ll> a(n+1,0);
  for(ll i=1;i<=n;i++) cin>>a[i];
  ll ans=0;
  for(ll z=1;z<=n;z++){//对于除不同的数之间,他们可以看成不同的部分,之间并不影响,所以可以分开算
  memset(dp,0,sizeof(dp));
  dp[0][0][0]=1;
  for(ll i=1;i<=n;i++){
    for(ll j=0;j<=z;j++){
      for(ll k=0;k<z;k++){
        dp[i][j][k]=(dp[i][j][k]+dp[i-1][j][k])%mod;
        if(j) dp[i][j][(k+a[i])%z]=(dp[i][j][(k+a[i])%z]+dp[i-1][j-1][k])%mod;//这里是对z求余,因为求的是最后z个数的情况
      }
    }
   }
   ans=(ans+dp[n][z][0])%mod;
  }
  cout<<ans;
}     
复制代码

 

 

E - Red and Blue Graph

题意: n个点,将其中m个点变成红色的,问最后两端颜色不同的绳子的个数是偶数的情况有多少种。

题解: 设红色的点的度数为S,设两端都是红色的边L条,两端颜色不同的边 R条,所以S=2*L+R;

         题意种说明R为偶数,2L也为偶数,所以S一定为偶数,所以我们只需要从度数为奇数和偶数的点中分别选择,根据组合数计算即可,其中要求余,组合数求余要用到逆元。

复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pll;
const int N=2e5+5;
const ll inf=1e18;
const ll mod=998244353;
ll a[N];
ll fact[N],infact[N];
ll qsm(ll x,ll y,ll mod){
  ll ans=1;
  while(y){
    if(y&1) ans=(ans*x)%mod;
    y>>=1;
    x=(x*x)%mod;
  }
  return ans%mod;
}
void init(){
   fact[0]=infact[0]=1;
   for(ll i=1;i<N;i++){
    fact[i]=(fact[i-1]*i)%mod;
    infact[i]=(infact[i-1]*qsm(i,mod-2,mod))%mod;
   }
}
ll solve(ll x,ll y){//逆元求组合数
  return fact[x]*infact[y]%mod*infact[x-y]%mod;
}
signed main(){
   ios::sync_with_stdio(false);
   cin.tie(0);cout.tie(0);
   init();
   ll n,m,k;cin>>n>>m>>k;
   vector<ll>sum(n+1,0);
   for(ll i=1;i<=m;i++){
    ll x,y;cin>>x>>y;
      sum[x]++;sum[y]++;
   }
   ll od=0;
   for(ll i=1;i<=n;i++){//找出度数为奇数的点

    if(sum[i]&1) od++;
   }
   ll ans=0;
   for(ll i=0;i<=k;i+=2){遍历奇数点的个数
    if(i<=od&&(k-i)<=n-od){
      ans=(ans+solve(od,i)%mod*solve(n-od,k-i)%mod)%mod;//组合数求答案
    }
   }
   cout<<ans;
}
   
复制代码

 

F - Erase and Rotate 

题意: 给出一个序列,最多k次操作,每次选择一种操作

  • 选择序列中的一个数删除
  • 将序列最后一个数放到开头位置

      问最多K次操作的情况下,可以转化成的字典大小最小的字符串是什么?

题解: 分成两种情况考虑

  • 不进行从后往前的操作,纯粹的从前往后遍历一遍,找出最小的序列
  • 进行从后往前的操作,找出位置 i 满足n-i+1<=k的最小的数字,将他放到开头,然后再进行一遍第一种情况的操作,其中,我们是将一部分序列往前,所以,对于一个后缀序列,如果它其中的某一项放到前面不会使字符串变小,那么久不需要提前,如果提前了再删除,就是两步操作,可以在提前之前就删除,就是一步操作。
  • 其中,如果我们把序列从前往后遍历了一遍,如果操作数仍有剩余,那么从后往前依次删除即可
复制代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pll;
const int N=2e5+5;
const ll inf=1e18;
const ll mod=998244353;
ll a[N];
deque<ll> p;
ll vis[N];
ll n,m,le[N];
vector<ll> solve(){
  vector<ll> res;
  ll sum=m;
  for(ll i=0;i<p.size();i++){
    while(!res.empty()&&res.back()>p[i]){
      if(vis[res.back()]) res.pop_back();//如果这一项是从后往前翻转呢一部分序列中包含的,那么就直接删掉,相当于在它还未翻转的时候删除,省了一次操作
      else if(sum) res.pop_back(),sum--;//如果不包含就删除并减少操作数
      else break;
    }
    res.push_back(p[i]);
  }
  while(sum--) res.pop_back();//将多余的操作次数用于从后往前删除
  return res;
}
signed main(){
   ios::sync_with_stdio(false);
   cin.tie(0);cout.tie(0);
   cin>>n>>m;
   for(ll i=1;i<=n;i++){
     ll x;cin>>x;p.push_back(x);
     le[x]=n-i+1;//每个数离尾部的距离,这里的数可能会覆盖,最后一定是最小的,也是最优的
   }
   vector<ll> ans=solve();
   ll beg=0;
   for(ll i=1;i<=n;i++){
    if(le[i]<=m){ beg=le[i];break;}//找出最优位置
   }
   while(beg--&&m){
    vis[p.back()]=1;
    p.push_front(p.back());
    p.pop_back();m--;
   }
   ans=min(ans,solve());
   for(auto it:ans) cout<<it<<" ";
}
   
复制代码

 

posted @   HHzp  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示