突然发觉我的flag里面好像还有“补完题写完简要题解”(悲
然后这都快半个月了居然只打了两场。怎么觉得墨水屏手机正在来的路上了(
来个ARC071的简要题解。
C题:
每个串开个桶,记录一下字母的出现次数,取个min,然后输出就好。
#include<bits/stdc++.h>
using namespace std;
char s[510];
int a[510][510];
int mi[510];
int n;
int main() {
memset(mi,0x3f,sizeof(mi));
scanf("%d",&n);
for(int i=1;i<=n;++i) {
scanf("%s",s+1);
int m = strlen(s+1);
for(int j=1;j<=m;++j) {
a[i][s[j]]++;
}
for(int j='a';j<='z';++j) {
mi[j] = min(mi[j], a[i][j]);
}
}
for(int i='a';i<='z';++i) {
for(int j=1;j<=mi[i];++j)
printf("%c",i);
}
return 0;
}
D题:
考虑俩相邻直线间夹的那一个长条的贡献。
假设这俩相邻直线是平行于X,记录这条的宽度为len。
把上下边界卡死,问题就转化为了,给你数轴上的n个点,你可以任意从里头选线段,求sum_length。
枚举中点,左右乘法原理一下。
放开上下边界的那个限制,发现只要对x和y分别算一算,乘一块就好。
#include<bits/stdc++.h>
using namespace std;
const int mod = 1000000007;
int n,m;
long long x[100010], y[100010];
long long xxis[100010], yxis[100010];
int main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%lld",&x[i]);
for(int i=1;i<=m;++i)
scanf("%lld",&y[i]);
for(int i=2;i<=n;++i)
x[i-1] = x[i] - x[i-1];
for(int i=2;i<=m;++i)
y[i-1] = y[i] - y[i-1];
n--, m--;
long long xsum = 0, ysum = 0;
for(int i=1;i<=n;++i) {
xxis[i] = 1ll * i * (n-i+1) % mod;
xsum = xsum + xxis[i] * x[i] % mod;
xsum %= mod;
}
for(int i=1;i<=m;++i) {
yxis[i] = 1ll * i * (m-i+1) % mod;
ysum = ysum + yxis[i] * y[i] % mod;
ysum %= mod;
}
long long ans = xsum * ysum % mod;
cout<<ans;
return 0;
}
E:
刚刚拿到手的时候愣了一下感觉没什么好办法。
手玩了一下,发觉可以通过如下路径交换相邻的俩AB:
A
B
−
>
B
B
B
−
>
B
B
A
A
−
>
B
A
A
A
A
−
>
B
A
AB -> BBB -> BBAA -> BAAAA -> BA
AB−>BBB−>BBAA−>BAAAA−>BA
那么我们只要让这两个子串的AB数量对应相同就好
支持
A
−
>
B
B
−
>
A
A
A
A
−
>
A
A->BB->AAAA->A
A−>BB−>AAAA−>A这种线路,所以显然是对3取模。
发觉有三个等价类,类里头可以互相转换。
分别是:
(0,0)(1,1)(2,2)
(1,0)(0,2)(2,1)
(0,1)(2,0)(1,2)
然后对俩字母分别前缀和,每次询问卡出来,判一判两个是不是在同一个等价类就行。
#include<bits/stdc++.h>
using namespace std;
int n, m, T;
char s[100010], t[100010];
int sa[100010], sb[100010], ta[100010], tb[100010];
int l1,r1,l2,r2;
//swap good
int type(int a,int b) {
if(a==b)
return 1;
if(a==1 && b==0)
return 2;
if(a==0 && b==2)
return 2;
if(a==2 && b==1)
return 2;
return 3;
}
int main() {
scanf("%s", s+1);
scanf("%s", t+1);
n = strlen(s+1), m = strlen(t+1);
for(int i=1;i<=n;++i) {
sa[i] = sa[i-1], sb[i] = sb[i-1];
if(s[i] == 'A')
sa[i]++;
else
sb[i]++;
}
for(int i=1;i<=m;++i) {
ta[i] = ta[i-1], tb[i] = tb[i-1];
if(t[i] == 'A')
ta[i]++;
else
tb[i]++;
}
scanf("%d",&T);
while(T--) {
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
int a1 = sa[r1] - sa[l1-1];
int b1 = sb[r1] - sb[l1-1];
int a2 = ta[r2] - ta[l2-1];
int b2 = tb[r2] - tb[l2-1];
a1 %= 3, b1 %= 3, a2 %= 3, b2 %= 3;
printf(type(a1,b1) == type(a2,b2) ? "YES\n" : "NO\n");
}
return 0;
}
F题:
妙妙的DP题。
首先发现一个事儿,如果你当前位置填了一个
i
>
1
i>1
i>1,且下个位置还不填1,那么整个序列就固定了。这种情况可以直接被加进ans里去。
那么,可能产生其他状态的序列就肯定是长成这样的:
3
,
1
,
1
,
1
,
5
,
1....1
,
9
,
1
,
1.....1
,
2
,
1
,
1
,
17
,
1
,
1.....1
3,1,1,1,5,1....1,9,1,1.....1,2,1,1,17,1,1.....1
3,1,1,1,5,1....1,9,1,1.....1,2,1,1,17,1,1.....1
一个数字,一堆1,一个数字,一堆1这样。
定义一个dp状态,用dp[i]表示,我们填完了前i-1个位置,且可以在第i个位置任选一个数字填进去的方案数。
显然有一个n^2的dp方法,到了i,枚举i填个啥,然后对应转。
几种转移:
填1,在i+1处获得任选。
填k>1,在i+k+1处获得任选(假装他小等于n
填k>1,往后塞一堆1,并且这堆1覆盖了n,序列固定
填k>1,而后一位填写任意一个非1数,序列固定。
思考了一下发现,我们在dp[i]对之后的dp数组的贡献,一定是除了dp[i+2]之后的所有dp状态同时加上一个数字。
那么维护一个后缀plus,然后给dp[i+2]减去这东西就好。
然后就变O(n)的了。
代码里的注释是写题的时候写的,为了防止自己被绕晕hhh
注意
i
=
=
n
i==n
i==n的时候,上面的 填非1再填非1 的转移是非法的,所以dp[n]要拿来特殊处理一下。
#include<bits/stdc++.h>
using namespace std;
int n, m, T;
const int mod = 1000000007;
long long dp[1000010];
long long ans = 0;
long long Splus = 0;
//当前做完i-1,且各自不同,第i个格子可以任选东西
//选1,在i+1处获得一个任选 1
//注意这里缺了一个dp[i+2]的任选位 1
//选k,而后在下一位填1,则在dp[i+k+1]处获得一个任选机会。 若i+k = n,则相当于钦定了一种ans 1
//选k,且k!=1,在一位填写任意一个非1数字,则固定了整个,直接加进ans。
//注意i = n-1的时候会发生变化。
int main() {
cin>>n;
dp[1] = 1;
for(int i=1;i<n;++i) {
dp[i] = dp[i] % mod + Splus;
dp[i] %= mod, dp[i] += mod, dp[i] %= mod;
long long pxis = i+1;
if(i == n-1) pxis = i;
ans += 1ll * pxis * dp[i] % mod;//当前位置填非1,然后填一堆1,然后1直接挤到n去了
ans += 1ll * dp[i] * (n-1) % mod * (n-1) % mod;//当前位置填非1,下一个填非1,直接拉满
ans %= mod;
dp[i+2] -= dp[i], Splus += dp[i];//发放任选机会
Splus %= mod;
}
dp[n] = dp[n] + Splus; dp[n] %= mod;
ans += dp[n] * n;
ans %= mod, ans += mod, ans %= mod;
cout<<ans;
}