「解题报告」2023-10-18 模拟赛
同色三角形
题目描述
有一个 \(n\) 个点的完全图,点分别被编号为 \(1 \sim n\),每个点都有 \(n−1\) 条边连向其它点。每条边有绿色或者红色两种颜色,现在我们知道每个点连着几条绿色边,几条红色边,但不知道每条边具体连接哪个点。
在完全图中任选三个点,观察它们之间的三条边,如果三条边颜色相同,那么我们就称其为“同色三角形”。现在牛牛已经知道了每个点连着几条绿色、红色边(保证这样的图一定存在),他想要知道图中最多有几个同色三角形。
很可惜,牛牛是色盲,所以这个问题只能交给你了。
输入描述:
第一行输入一个正整数 \(n\),表示共有 \(n\) 个点。
接下来包含 \(n\) 行,每行给两个数字 \(a,b\),其中第 \(i\) 行的数字表示第 \(i\) 个点的红色边、绿色边数量,保证 \(a+b=n−1\)。
输出描述:
输出一行一个整数表示答案。
示例1
输入
4
1 2
2 1
2 1
3 0
输出
1
说明
备注:
对于 \(10\%\) 的数据,有 \(n=3\)
对于 \(20\%\) 的数据,有 \(n=4\)
对于 \(30\%\) 的数据,有 \(n=5\)
对于 \(50\%\) 的数据,有 \(n \le 20\)
对于 \(70\%\) 的数据,有 \(n \le 1000\)
对于 \(100\%\) 的数据,有 \(3 \le n \le 3 \times 10^5\)
容斥原理,用总的方案减去异色的方案。
总的方案:\(C_{n}^{3}\),异色方案:\(\dfrac{\sum_{i = 1}^{n}(a_i \times b_i)}{2}\)。
// The code was written by yifan, and yifan is neutral!!!
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug puts("NOIP rp ++!");
#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c))
template<typename T>
inline T read() {
T x = 0;
bool fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
template<typename T>
void write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9) {
write(x / 10);
}
putchar(x % 10 + '0');
}
template<typename T>
void print(T x, char c) {
write(x);
putchar(c);
}
const int N = 3e5 + 5;
ll n;
int a[N], b[N];
int main() {
n = read<int>();
ll ans = 0;
rep (i, 1, n, 1) {
a[i] = read<int>(), b[i] = read<int>();
ans = (ans + 1ll * a[i] * b[i]);
}
ans /= 2;
ll sum = 1;
sum = n * (n - 1) * (n - 2);
sum /= 6;
cout << sum - ans << '\n';
return 0;
}
任务
白浅妹妹有 \(n\) 个任务和一个长度为 \(n\) 的数组 \(a\),数组中的第 \(i\) 个数字记为 \(a_i\)。
白浅妹妹的第 \(i\) 个任务是用一个字符串 \(s_i\) 描述的,意思是需要构造一个长度为 \(a_i\) 的字符串 \(S\),使得 \(s_i\) 是 \(S\) 的子序列。
现在给定 \(q\) 次操作,每次给出三个正整数。其中第一个正整数为 \(0\) 或 \(1\),表示操作类型。
操作类型 \(0\):给定 \(0,pos,val\),表示将 \(a_{pos}\) 修改为 \(val\)。
操作类型 \(1\):给定 \(1,l,r\),表示询问白浅妹妹从第 \(l\) 个任务一直完成到第 \(r\) 个任务,她总共有多少种方案构造字符串。
请注意,在白浅妹妹完成 \(l \sim r\) 这些字符串的过程的若干方案中,只要有任何一个任务构造的字符串不同,我们则认为这是不同的方案。
由于答案可能很大,请输出答案对 \(998244353\) 取模之后的结果。
输入描述:
第一行输入两个正整数 \(n,q\)。
接下来 \(n\) 行,分别表示所有的 \(s_i\)
接下来一行给出 \(n\) 个由空格隔开的正整数,表示数组 \(a\),
接下来 \(q\) 行,每行给出三个正整数,意义如题面所示。三个正整数中的第一个正整数为 \(0\) 或 \(1\),表示两种操作。
输出描述:
对于每个操作 \(1\),输出一行一个正整数表示答案对 \(998244353\) 取模之后的结果。
示例1
输入
3 4
ab
b
c
3 2 1
1 1 1
1 2 2
1 1 2
1 3 3
输出
76
51
3876
1
示例2
输入
2 3
a
bc
2 6
1 1 2
0 2 3
1 2 2
输出
315251451
76
备注:
对于 \(20\%\) 的数据,有 \(1 \le n,q \le 10,1 \le a_i,|s_i| \le 5\)
对于 \(50\%\) 的数据,有 \(1 \le n,q \le 10^3,\sum|s_i| \le 1000\)
对于 \(70\%\) 的数据,有 \(1 \le n,a_i \le 10^5,1 \le q \le 5 \times 10^5,1 \le |s_i| \le 10^3\)
对于 \(100\%\) 的数据,有 \(1 \le n,a_i \le 10^5,1 \le q \le 5 \times 10^5,\sum |s_i|≤3 \times 10^5\)
我们考虑如何求出包含 \(s\) 这个子序列的长为 \(L\) 的字符串 \(S\) 有多少个。令 \(f(i,j)\) 表示目前枚举到了 \(S\) 的第 \(j\) 位,匹配到了 \(s\) 的第 \(i\) 位的方案数。那么 \(f(i,j)\)
(要么第 \(j\) 位匹配上了,那么 \(j\) 的取值就只有一种,要么第 \(j\) 位没有匹配上,那么取值就有 \(25\) 个)
所以我们发现答案只与 \(|s_i|\) 有关,与 \(s\) 内容无关。如果处理好这些 \(f\) 的话,将可以获得 \(50 \sim 70\) 的暴力分。
100pt
通过大眼观察法,我们发现
因为 \(\sum |s_i| \le 3 \times 10^5\),所以 \(|s_i|\) 至多有 \(\sqrt{6 \times 10^5}\) 种。那么我们就可以维护一个单点修改,区间查询乘积的线段树了。
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const ll Maxn=1e5,Maxs=3e5,mod=998244353,Maxf=8e2;
ll n,q,a[Maxn+10];
int dp[Maxf+10][Maxn+10];
char s[Maxs+10];
bool cmp(ll x,ll y){return x<y;}
ll maxll(ll x,ll y){return x>y?x:y;}
ll minll(ll x,ll y){return x<y?x:y;}
ll tub[Maxs+10],pos[Maxn+10],lenf,len[Maxf+10];
ll tb_25[Maxn+10],jc[Maxn+10];
ll inv[Maxn+10],inv_jc[Maxn+10];
vector<ll>id[Maxn+10];
ll C(ll x,ll y){//x个中选y个
return ((jc[x]*inv_jc[y]%mod)*inv_jc[x-y])%mod;
}
ll al,ar;
ll tr[5*Maxn+10];
void build(ll l,ll r,ll p){
if(l==r){
tr[p]=dp[pos[l]][a[l]];
return ;
}
ll mid=(l+r)>>1;
build(l,mid,p<<1);
build(mid+1,r,p<<1|1);
tr[p]=tr[p<<1]*tr[p<<1|1]%mod;
}
ll query(ll l,ll r,ll p){
if(r<al||ar<l)
return 1ll;
if(al<=l&&r<=ar)
return tr[p];
ll ret=1,mid=(l+r)>>1;
if(mid>=al)
ret=ret*query(l,mid,p<<1)%mod;
if(mid+1<=ar)
ret=ret*query(mid+1,r,p<<1|1)%mod;
return ret%mod;
}
void update(ll l,ll r,ll p){
if(r<al||ar<l)
return ;
if(l==al&&r==ar&&l==r){
tr[p]=dp[pos[l]][a[l]];
return ;
}
ll mid=(l+r)>>1;
if(mid>=al)
update(l,mid,p<<1);
if(mid+1<=ar)
update(mid+1,r,p<<1|1);
tr[p]=tr[p<<1]*tr[p<<1|1]%mod;
}
int main(){
tb_25[0]=1;
for(ll i=1;i<=Maxn;i++)
tb_25[i]=(tb_25[i-1]*25ll)%mod;
jc[1]=inv[1]=inv_jc[1]=1;
jc[0]=1,inv_jc[0]=1,inv[0]=1;
for(ll i=2;i<=Maxn;i++){
jc[i]=(jc[i-1]*i)%mod;
inv[i]=((mod-mod/i)*inv[mod%i])%mod;
inv_jc[i]=inv[i]*inv_jc[i-1]%mod;
}
scanf("%lld%lld",&n,&q);
ll slen=0;
for(ll i=1;i<=n;i++){
scanf("%s",s+1);
slen=strlen(s+1);
tub[slen]++;
id[slen].push_back(i);
}
for(ll i=1;i<=Maxs;i++){
if(tub[i]==0)
continue;
lenf++;
len[lenf]=i;
for(ll i1=0;i1<tub[i];i1++)
pos[id[i][i1]]=lenf;
}
for(ll i=1;i<=lenf;i++)
for(ll i1=len[i];i1<=Maxn;i1++)
dp[i][i1]=(int)((C(i1-1,len[i]-1)*tb_25[i1-len[i]])%mod+dp[i][i1-1]*26ll%mod)%mod;
for(ll i=1;i<=n;i++)
scanf("%lld",&a[i]);
ll l=0,r=0,cs=0,sum=0;
build(1,n,1);
for(ll i=1;i<=q;i++){
sum=1;
scanf("%lld%lld%lld",&cs,&l,&r);
if(cs==1){
al=l,ar=r;
printf("%lld\n",query(1,n,1));
}
else{
al=ar=l,a[l]=r;
update(1,n,1);
}
}
return 0;
}
加法方案
链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
题目描述
白浅妹妹正在学习加法,但是老师只给了她一个数字 \(n\),她没法对一个数字做加法运算,于是她从 \(n\) 中取出若干个数位(至少 \(1\) 个),然后按照原来的相对顺序拼接组成新一个数字 \(x\),剩余的数位也按原来的相对顺序组成另一个数字 \(y\),将两个数字 \(x,y\) 求和。
例如 \(12345\) 可以拿出 \(24\),剩下 \(135\),求和 \(24+135=159\)。
求所有拆数字的方案的和对 \(998244353\) 取模后的结果。
允许把 \(n\) 中所有的数位全部取出,此时 \(n\) 变成 \(0\)。
记 \(n \in [10^{m−1},10^m)\)。\(n\) 从高到低位记为 \(a_{m−1}\) 到 \(a_0\),即\(n = a_{m−1} a_{m−2} \dots a_0\)。记 \(S\) 是 \(\{0,1,2,⋯,m−1\}\) 的子集。每个 \(S\) 对应特定的 \(a\) 的子序列,即 \(S=\{0,2,3\}\),则 \(x=a_3a_2a_0\)。两个方案不同等价于 \(S\) 不同。
输入描述:
输入包含一行。
第一行输入一个正整数 \(n\)。
输出描述:
输出一个数,即所有拆数字的方案的和对 \(998244353\) 取模后的结果。
示例1
输入
123
输出
231
说明
取 \(1\) 个数位:\(12+3=15,23+1=24,13+2=15\)
取 \(2\) 个数位:\(3+12=15,1+23=24,2+13=15\)
取 \(3\) 个数位:\(0+123=123\)
全部求和得到 \(231\)
示例2
输入
221
输出
359
备注:
-
对于测试点 \(1\):\(1 \le n < 10^{100}\)。
-
对于测试点 \(2 \sim 5\):\(1 \le n < 10^{1000}\)。
-
对于测试点 \(6∼10\):\(1 \le n < 10^{100000}\)。
60 分暴力:
可以发现答案即为这个式子:
// The code was written by yifan, and yifan is neutral!!!
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define bug puts("NOIP rp ++!");
#define rep(i, a, b, c) for (int i = (a); i <= (b); i += (c))
#define per(i, a, b, c) for (int i = (a); i >= (b); i -= (c))
template<typename T>
inline T read() {
T x = 0;
bool fg = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
fg |= (ch == '-');
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return fg ? ~x + 1 : x;
}
template<typename T>
void write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9) {
write(x / 10);
}
putchar(x % 10 + '0');
}
template<typename T>
void print(T x, char c) {
write(x);
putchar(c);
}
const int N = 1e5 + 5;
const int mod = 998244353;
ll ans;
char s[N];
int a[N];
ll fac[N], inv[N];
ll qpow(ll x, ll y) {
ll res = 1;
while (y) {
if (y & 1) {
res = res * x % mod;
}
y >>= 1;
x = x * x % mod;
}
return res % mod;
}
ll C(int n, int m) {
if (n < m) return 0;
return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
int main() {
scanf("%s", s);
int len = strlen(s);
rep (i, 1, len, 1) {
a[i] = s[i - 1] - '0';
}
fac[0] = 1;
rep (i, 1, len, 1) {
fac[i] = fac[i - 1] * i % mod;
}
inv[len] = qpow(fac[len], mod - 2);
per (i, len, 1, 1) {
inv[i - 1] = inv[i] * i % mod;
}
rep (i, 1, len, 1) {
ll res = 0;
rep (j, 0, len - i, 1) {
res = (res + C(len - i, j) * qpow(10, j) % mod) % mod;
}
res = (res * qpow(2, i) % mod * a[i]) % mod;
ans = (ans + res) % mod;
}
rep (i, 1, len, 1) {
ans = (ans - a[i] * qpow(10, len - i) % mod + mod) % mod;
}
print(ans, '\n');
return 0;
}
发现若 \(x\) 可以为空,则 \(x\) 可以和 \(y\) 构成双射,则最终答案即为 \(2 \sum x−原数\)。
考虑求 \(\sum x\),可以考虑每一位的贡献,则有从高到低第 \(i\) 位的贡献为
考虑这个 \(\sum_{j = 0}^{n - i} 10^j \dbinom{n - i}{j}\) 式子的组合意义,记 \(x\),即为给出标号分别为 \(1 \sim x\) 的 \(x\) 个小球,选出 \(j\) 个小球放入 \(10\) 个盒子中。
考虑钦定剩下的 \(x−j\) 个小球放入第 \(11\) 个盒子,则该式即为将 \(x\) 个小球放入 \(11\) 个盒子中的方案数,即 \(11^{n−i}\)。
预处理出 \(2\) 和 \(11\) 的幂次即可做到 \(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5,M=998244353;
using ll=long long;
using ul=unsigned long long;
void add(int &x,int y) {
(x+=y)>=M&&(x-=M);
}
void add(int &x,ul y,int z) {
x=(y*z+x)%M;
}
int qp(ul a,int x=M-2) {
int res=1;
for(; x; x>>=1,a=a*a%M)
(x&1)&&(res=a*res%M);
return res;
}
long long n,pw[N],ans;
string s;
int main() {
long long number = 0, k = 2;
cin >> s;
for(int i = 0; i < s.size(); i++)
number = (number * 10 + (s[i] - '0')) % M;
reverse(s.begin(),s.end());
pw[0] = 1;
for(int i = 1; i <= s.size(); i++)
pw[i] = 2 * pw[i-1] % M;
for(int i = 0; i < s.size(); i++){
long long now = k * (s[i] - '0') % M * pw[s.size() - i - 1];
k = 11 * k % M;
ans = (ans + now) % M;
}
ans -= number;
if(ans < 0)
ans += M;
cout << ans << endl;
}