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<<" "; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现