文章目录
- [F. Da Mi Lao Shi Ai Kan De](https://codeforces.com/gym/104090/problem/F)
- [D. Money Game](https://codeforces.com/gym/104090/problem/D)
- [A. Modulo Ruins the Legend](https://codeforces.com/gym/104090/problem/A)
- [C. No Bug No Game](https://codeforces.com/gym/104090/problem/C)
- [K. Master of Both](https://codeforces.com/gym/104090/problem/K)
F. Da Mi Lao Shi Ai Kan De
题意:
格莱美已加入 n+1 个QQ 群。编号为 0∼n 的 QQ 群。Rice 老师在群 0 中。
每天,格莱美的朋友们都会向 1∼n 中的一些群发送信息,格莱美会选择米老师喜欢的信息转发到 0 群。
在这里,我们将信息定义为由小写字母组成的字符串,当且仅当字符串 "bie "是信息的子串时,赖斯老师才会喜欢该信息。
现在,给定 1∼n 组中的信息,格莱美将依次搜索从 1 组到 n组的信息,逐一检查组中的信息。对于每条信息,如果大米老师喜欢,并且没有在 0 组中出现过,就把它挑出来,转发到 0 组。这里的 "出现 "指的是同一条信息曾经转发到过 0 组。
请按顺序输出格莱美将转发到组 0 的所有邮件。对于每个组,如果格莱美无法挑选出任何信息,则单行输出 “大米老师,该玩玄心冲击了!”。
模拟即可,将字符串放入map或set里,如果怕会超的话,也可以用字符串哈希
#include <bits/stdc++.h>
using namespace std;
map<string, int> mp;
bool f(string s){for(int i=1;i<s.size()-1;i++){if(s[i]=='i'&&s[i-1]=='b'&&s[i+1]=='e') return true;}return false;
}
void solve() {int n; cin >> n;bool ed = false;while (n --) {string s; cin >> s;if (!f(s)) continue;if (!mp[s]) {cout << s << endl;mp[s] = 1;ed = true;}}if (!ed) {cout << "Time to play Genshin Impact, Teacher Rice!" << endl;}
}
int main() {int T; cin >> T;while (T --) solve();return 0;
}
D. Money Game
题意:
Putata 和 Budada 正在组织一场有 n 名玩家参加的游戏。每个玩家都有一些存款,这些存款都是实数。玩家 i 一开始有 ai 个存款。在每一轮游戏中,会依次发生以下情况:
- 玩家 1 给玩家 2 玩家 1 存款的一半。
- 玩家 2 给玩家 3 玩家 2 存款的一半。
- …
- 玩家 n−1 给玩家 n 玩家 n−1 押金的一半。
- 玩家 n 给玩家 1 玩家 n 存款的一半。
n 位玩家正好下了 202 2 1204 2022^{1204} 20221204 轮。普塔塔想知道游戏结束后每个玩家有多少存款。请编写一个程序来回答他的问题。
轮数超级大,这题应该是规律题,要么有周期,要么最后是不动点
打表发现,最终一定是:第一个数是sum/(n+1)的两倍,其它数均是sum/(n+1),而且收缩得很快
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
const int N=1e6+10;
double a[N];
int n;
void solve() {cin>>n;double sum=0;for(int i=1;i<=n;i++) cin>>a[i],sum+=a[i];double now=sum/(n+1);printf("%.10f ",now * 2);for(int i=1;i<=n - 1;i++) printf("%.10f ",now);cout<<endl;
}int main() {int T=1; while (T --) solve();return 0;
}
A. Modulo Ruins the Legend
题意:
格莱美有一个整数序列 a1,a2,…,an 。她认为序列中的元素过多,因此决定在序列中加入算术级数。从形式上看,她可以选择两个非负整数 s,d ,并在每个 k∈[1,n] 的基础上将 s+kd 加到 ak 。
由于我们想破坏图例,请告诉她操作后元素模 m 的最小和。注意,应在取模后将和最小化。
先把式子推导一下
令sum=a[1]+a[2]+...+a[n]
ans=(sum+n*s+(1+n)*n/2*d)%m
根据裴蜀定理,n*s+(1+n)*n/2*d=k1*gcd(n,(1+n)*n/2);
故ans=(sum+k1*gcd(n,(1+n)*n/2))%m
此时有一个trick:
假设出现a%b=res的话,可以转化为a=k*b+res
或者res=a-k*b
根据res=a-k*b,这里的ans是模之后的,其实就相当于res
故ans=sum+k1*gcd(n,(1+n)*n/2)-k2*m
再次用裴蜀定理,k1*gcd(n,(1+n)*n/2)-k2*m=k1*k2*gcd(n,(1+n)*n/2,m)=k*gcd(n,(1+n)*n/2,m)
故ans=sum+k*gcd(n,(1+n)*n/2,m)
由于ans表示的是模之后的,所以这已经是最终的表达式了,也就是说是模之后的,最小肯定是sum-若干个gcd(n,(1+n)*n/2,m),也就是ans=sum%gcd(n,(1+n)*n/2,m)
算出最小值之后,我们需要反推出s和d
通过扩展欧几里得可以反推:
n*s+(1+n)*n/2*d=k1*gcd(n,(1+n)*n/2)
k1*gcd(n,(1+n)*n/2)-k2*m=k*gcd(n,(1+n)*n/2,m)=ans-sum
由第二个式子可以推出k1,然后代入第一个式子推出s和d
由于s和d都要大于0,那么在模意义下就先模再加模再模就可以转化为正数了
有可能会爆long long,因为最终是模意义下的答案,所以中途都可以模
说实话看不出来为什么会爆long long,但是如果是求模意义下的答案的话,最好在中途一直模,防止中途爆long long
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
int n,m;
int exgcd(int a,int b,int &x,int &y){if(b==0){x=1,y=0;return a;}int d=exgcd(b,a%b,y,x);y-=a/b*x;return d;
}
int gcd(int a,int b){if(b==0) return a;return gcd(b,a%b);
}
void solve() {cin>>n>>m;int sum=0;for(int i=1;i<=n;i++) cin>>a[i],sum+=a[i];int g1=gcd(n,(1+n)*n/2);int g2=gcd(g1,m);int ans=sum%g2;int k1,k2;exgcd(g1,m,k1,k2);k1*=(ans-sum)/g2%m;k1%=m;int s,d;exgcd(n,(1+n)*n/2,s,d);s*=k1,d*=k1;s=(s%m+m)%m,d=(d%m+m)%m;cout<<ans<<endl;cout<<s<<' '<<d<<endl;
}
signed main() {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int t=1;
// cin>>t;while(t--) {solve();}return 0;
}
或者用__int128,注意快写里面有putchar(),不要关闭同步流了
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
int n,m;
int exgcd(int a,int b,__int128 &x,__int128 &y){if(b==0){x=1,y=0;return a;}int d=exgcd(b,a%b,y,x);y-=a/b*x;return d;
}
int gcd(int a,int b){if(b==0) return a;return gcd(b,a%b);
}
int read()
{int x=0,f=1;char ch=nc();while(ch<48||ch>57){if(ch=='-')f=-1;ch=nc();}while(ch>=48&&ch<=57)x=x*10+ch-48,ch=nc();return x*f;
}
void write(int x)
{if(x<0)putchar('-'),x=-x;if(x>9)write(x/10);putchar(x%10+'0');return;
}
void solve() {cin>>n>>m;int sum=0;for(int i=1;i<=n;i++) cin>>a[i],sum+=a[i];int g1=gcd(n,(1+n)*n/2);int g2=gcd(g1,m);int ans=sum%g2;__int128 k1,k2;exgcd(g1,m,k1,k2);k1*=(ans-sum)/g2;__int128 s,d;exgcd(n,(1+n)*n/2,s,d);s*=k1,d*=k1;s=(s%m+m)%m,d=(d%m+m)%m;cout<<ans<<endl;write(s);cout<<' '; write(d);cout<<endl;
}
signed main() {
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);int t=1;
// cin>>t;while(t--) {solve();}return 0;
}
C. No Bug No Game
题意:
Putata 正在筹备由国际电脑游戏公司(ICPC)举办的 RPG 职业联赛(RPL)。在这款 RPG 游戏中,玩家将同时佩戴 n 件物品。每件道具都能为玩家提供几分力量。游戏中有一个魔法 BUFF,它可以升级每件道具,使它们能够提供几分额外的力量。
不过,这个 BUFF 是有限制的,它最多只能提供 k 点能量。从形式上看,假设玩家一开始什么都没穿,然后会一件一件地穿上所有 n 物品。游戏服务器将根据玩家穿戴的排列方式,逐一扫描所有这些 n 物品。当服务器扫描到 i -th件物品时,可以提供 pi 点能量,让 ∑ j = 1 i − 1 \sum_{j=1}^{i-1} ∑j=1i−1 p j p_j pj 表示之前扫描到的总能量:
- 如果 sum+ p i p_i pi≤k ,则整个物品都将升级。BUFF 将提供 w i , p i w_{i,p_i} wi,pi 点额外能量。
- 如果是 sum≥k ,物品不会升级。该 BUFF 将不提供任何效果。
- 否则,只会升级部分物品。该 BUFF 将提供 w i , k − s u m w_{i,k−sum} wi,k−sum 点额外能量。
普塔塔很聪明,他很快就意识到,他可以调整佩戴这些 n 物品的排列组合,以获得更多点的奖励力量!不幸的是,Putata 不知道最佳排列方式,请编写一个程序来帮助他。
游戏服务器执行魔法缓冲的行为是一个错误。游戏代码能正常工作全靠 BUG,所以 w i , a w_{i,a} wi,a> w i , b w_{i,b} wi,b 有可能是 a<b 。
翻译一下就是
有n个物品,背包容量为k,每个物品重量为 p i p_i pi,取的重量不同,获得的价值也不同,从1到 p i p_i pi分别为 w i j w_{ij} wij,如果当前背包容量足够,则必须取完整的重量,否则才可以取部分重量来填满剩余的背包容量,问能取得的最大价值是多少?
有限制的选择-->背包
不是经典的背包,则视为背包变形
有一个关键的点,如果背包容量足够则必须取完整的重量,否则取部分重量,所以取部分重量的最多有一个
所以我们枚举哪一个是取部分的,并且是取的部分的体积是多少,其它的话则正常01背包,假设部分取了x,那么枚举前面部分体积为j,则后面部分体积为k-x-j,分别预处理前缀后缀背包即可
类似的背包变形,前缀后缀背包的是2022南京B. Ropeway
关于初始化,如果是求最大,那么全部初始化为负无穷,求最小初始化为正无穷
然后看循环最开始需要什么就初始化什么
这题需要初始为负无穷,正常的背包其实全是0是可以的,但是
ans=max(ans,dp1[i-1][pre]+w[i][j]+dp2[i+1][last]);
如果w[i][j]比较大,但是dp1[i-1][pre]不合法,而它为0,那么就把不合法的记录下来了,初始化为负无穷就不会max到ans里了
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=3010;
int p[N];
int w[N][20];
int n,k;
int dp1[N][N],dp2[N][N];
void solve() {cin>>n>>k;for(int i=0;i<=n+1;i++){for(int j=0;j<=k;j++){dp1[i][j]=dp2[i][j]=-1e18;}}int sum=0;for(int i=1;i<=n;i++){cin>>p[i];sum+=p[i];for(int j=1;j<=p[i];j++) cin>>w[i][j];}//全选if(sum<=k){int ans=0;for(int i=1;i<=n;i++) ans+=w[i][p[i]];cout<<ans<<endl;return;}//前缀背包dp1[0][0]=0;for(int i=1;i<=n;i++){for(int j=0;j<=k;j++){dp1[i][j]=dp1[i-1][j];if(j>=p[i]) dp1[i][j]=max(dp1[i][j],dp1[i-1][j-p[i]]+w[i][p[i]]);}}//后缀背包dp2[n+1][0]=0;for(int i=n;i>=1;i--){for(int j=0;j<=k;j++){dp2[i][j]=dp2[i+1][j];if(j>=p[i]) dp2[i][j]=max(dp2[i][j],dp2[i+1][j-p[i]]+w[i][p[i]]);}}int ans=0;for(int i=2;i<n;i++){for(int j=1;j<=p[i];j++){//枚举选了第i个物品的部分体积为jint res=k-j;//剩下的体积if(res<0) continue;for(int pre=0;pre<=res;pre++){//前缀体积int last=res-pre;//后缀体积if(last<0) continue;ans=max(ans,dp1[i-1][pre]+w[i][j]+dp2[i+1][last]);}}}for(int j=1;j<=p[1];j++){int res=k-j;if(res<0) continue;ans=max(ans,dp2[2][res]+w[1][j]);}for(int j=1;j<=p[n];j++){int res=k-j;if(res<0) continue;ans=max(ans,dp1[n-1][res]+w[n][j]);}cout<<ans<<endl;
}
signed main() {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int t=1;
// cin>>t;while(t--) {solve();}return 0;
}
K. Master of Both
题意:
慧博特教授是弦理论和高级数据结构方面的大师,因此他提出了一个有趣的问题。给定一个仅由小写英文字母组成的 n 字符串序列,如果按词典顺序比较,这个序列中有多少个倒序?
作为慧机器人的得意门生,普塔塔和布达达分别掌握了高超的字符串理论和高级数据结构技巧,他们一起轻松地解决了这个问题。然而,在 q 个不同的平行宇宙中,字母表中的字符并不是按照原来的顺序出现的。
从形式上看,每个宇宙中的字母表都是一个字符串,它是 26 个小写英文字母的排列组合,表示每个字符出现的顺序。
当且仅当以下条件之一成立时,字符串 aa 在词法上小于字符串 b :
- a 是 b 的前缀,但 a≠b ;
- 在 a 和 b 不同的第一个位置上,字符串 a 的字母在字母表中的出现时间早于 b 中的相应字母。
长度为 n 的序列 a 中的倒序数是 (i,j) 中 1≤i<j≤n , aj<ai 的有序对的个数。
请帮助每个宇宙中的 Putata 和 Budada 解决这个问题
如果对于每个宇宙,分别求逆序对肯定是超时的,必须提前预处理
字典序比较的一大特征就是前面均相同,然后出现第一个不一样的,也就是说有一个公共前缀
公共前缀想到字典树
比如枚举到某一个点x时,它的父亲节点的其它子节点和它有公共前缀,直到当前才不一样,并且顺序早于它,所以我们可以提前记录cnt[pre] [x]的个数,也就是pre在前,x在后,它们不一样,它们的前面是公共前缀,由于只有26个字母,所以cnt[30] [30]就够了
然后在处理询问时,我们就枚举当前宇宙规则下前面是大的字母pre,后面是小的字母last,看cnt[pre] [last]即为贡献
注意,这样只能比较有公共前缀,且当前字符不同的情况,但是可能出现abc,ab这一情况,是记录不了的,所以每个字符串后面加一个比26个字母都都小的’a’-1
我们的数组要开到总字符串的长度,由于每个字符串后面加了一个’a’-1,所以光1e6是不够的,再加5e5
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e6+5e5+10;
int f[N][30];
int cnt[N];
int idx;
int n,q;
int ans[30][30];
//插入字符串
void insert(string s){int p=0;for(int i=0;i<(int)s.size();i++){int j=s[i]-'a'+1;if(!f[p][j]) f[p][j]=++idx;for(int k=0;k<=26;k++){if(k==j) continue;ans[k][j]+=cnt[f[p][k]];}p=f[p][j];cnt[p]++;}
}
void solve() {cin>>n>>q;for(int i=0;i<n;i++){string s;cin>>s;s+='a'-1;insert(s);}while(q--){string s;cin>>s;int res=0;for(int i=0;i<26;i++){res+=ans[s[i]-'a'+1][0];for(int j=0;j<i;j++){res+=ans[s[i]-'a'+1][s[j]-'a'+1];}}cout<<res<<endl;}
}
signed main() {ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);int t=1;
// cin>>t;while(t--) {solve();}return 0;
}