A.01 Sequence
题意:对于一个长度为
3
3
3 的倍数的,元素只有01的环,你每次可以选择一个
1
1
1 删除以这个
1
1
1 为中心的相邻三个元素。你可以选择将环当中的部分
0
0
0 变成
1
1
1 ,求最少的选择数字数量使得你能够将这个环删除完毕。
给定一个长度为
n
n
n 的01序列,
q
q
q 次询问,每次询问一个区间。每次询问一个区间,表示询问的环。
(
3
≤
n
≤
1
e
6
,
1
≤
q
≤
1
e
6
)
(3 \leq n \leq 1e6, 1 \leq q \leq 1e6)
(3≤n≤1e6,1≤q≤1e6)
题解:题意显然可以转换成
l
e
n
/
3
−
环上选择至多的互不相邻的
1
的数量
len/3 - 环上选择至多的互不相邻的1的数量
len/3−环上选择至多的互不相邻的1的数量。通过线段树维护这个值,直接区间查询即可。
注意,题目询问是个环 ,最后得到的答案应该满足,不同时选左右端点的1,且上述式子可能求出来负数,要和
0
0
0 取
m
a
x
max
max。
复杂度:
O
(
n
+
q
l
o
g
n
)
O(n+qlogn)
O(n+qlogn)
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N =1000005;
string str;
namespace SegTree{
#define ls rt << 1
#define rs rt << 1 | 1
#define lson ls, l, mid
#define rson rs, mid + 1, r
struct Info{
int lmax, rmax, lrin, lrout;
friend Info operator+ (Info a, Info b){
return {
max(a.lmax+max(b.lmax,b.lrout),a.lrin+b.lrout),
max(max(a.rmax,a.lrout)+b.rmax,a.lrout+b.lrin),
max({a.lmax+b.rmax,a.lrin+b.rmax,a.lmax+b.lrin}),
max({a.rmax+b.lrout,a.lrout+b.lrout,a.lrout+b.lmax})
};
}
}tr[N << 2];
void push_up(int rt){ tr[rt] = tr[ls] + tr[rs]; }
void build(int rt, int l, int r){
if(l == r){
if(str[l-1]=='1'){
tr[rt]={0,0,1,0};
}
return;
}
int mid = l + r >> 1;
build(lson), build(rson);
push_up(rt);
}
Info query(int rt, int l, int r, int L, int R){
if(l >= L && r <= R) return tr[rt];
int mid = l + r >> 1;
if(mid >= R) return query(lson, L, R);
if(mid < L) return query(rson, L, R);
return query(lson, L, R) + query(rson, L, R);
}
}
using SegTree::build;
using SegTree::query;
inline void solve(){
int n, q; cin >> n >> q >> str;
build(1, 1, n);
for(int i = 1; i <= q; i++){
int l, r; cin >> l >> r;
auto res = query(1, 1, n, l, r);
cout << max((r-l+1)/3-max({res.lmax, res.rmax,res.lrout}),0ll)<< endl;
}
}
signed main(){
ios_base::sync_with_stdio(false), cin.tie(0);
int t = 1; // cin >> t;
while(t--) solve();
return 0;
}
C.Delete the Tree
给一个树,你可以选择两种操作
删除:删除一个点以及所有与他相邻的边
收缩:将一个有两条边的结点与他有的边删除,并连接两个本来与他相连的结点。
求最少的删除操作删除整棵树。
题解:签到题,对于每个度数大于等于 2 2 2 的结点在删除过程中都可能使得度数等于 2 2 2 ,所以答案就是度数小于等于 1 1 1 的节点数目。
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
const int N = 1e6 + 10;
int d[N],ans;
inline void solve(){
int n; cin >> n; ans = 0;
memset(d, 0, (n + 2) * sizeof(int));
for(int i = 1; i < n; i++){
int u, v; cin >> u >> v;
d[u]++;d[v]++;
}
for(int i = 1; i <= n; i++){
if(d[i] <= 1) ans++;
}
cout << ans << endl;
}
signed main(){
ios_base::sync_with_stdio(false), cin.tie(0);
int t = 1; cin >> t;
while(t--) solve();
return 0;
}
G.Read the Documentation
题意:给定一个数字
n
n
n 表示按照顺序有
n
n
n个物品,每个物品有一个权值
w
i
w_i
wi,有费用
t
t
t。
对于选择物品要求有
不能选择连续的五个物品
作为连续的第 i i i 个物品需要花费 x i x_i xi 的费用
求获得的权值最大是多少。
(
1
≤
n
≤
100
,
1
≤
q
≤
1
e
9
)
(1 \leq n \leq 100,1 \leq q \leq 1e9)
(1≤n≤100,1≤q≤1e9)
题解:简单背包。
令
d
p
[
i
]
[
j
]
[
k
]
[
l
]
[
m
]
dp[i][j][k][l][m]
dp[i][j][k][l][m] 表示,前
i
i
i 个数,
j
j
j 个
1
1
1 ,
k
k
k 个
2
2
2 ,
l
l
l 个
3
3
3 ,
m
m
m 个
4
4
4 的方案的最大值
满足
j
∗
2
+
k
∗
3
+
l
∗
4
+
m
∗
5
<
=
i
+
1
&
&
s
u
m
1
∗
j
+
s
u
m
2
∗
k
+
s
u
m
3
∗
l
+
s
u
m
4
∗
m
≤
t
j*2+k*3+l*4+m*5<=i+1 \&\& sum_1*j+sum_2*k+sum_3*l+sum_4*m \leq t
j∗2+k∗3+l∗4+m∗5<=i+1&&sum1∗j+sum2∗k+sum3∗l+sum4∗m≤t
暴力转移即可(转移式子可见代码)。
复杂度
O
(
n
5
120
)
O(\frac{n^5}{120})
O(120n5)
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,m,t,ans;
int x[10],a[105];
int dp[102][55][35][27][22];
void solve(){
cin>>n>>t;
for(int i=1;i<=4;i++){
cin>>x[i];
x[i]+=x[i-1];
}
for(int i=1;i<=n;i++){
cin>>a[i];
a[i]+=a[i-1];
}
for(int i=1;i<=n;i++){
for(int j=0,sum1=0,res1=0;res1<=t&&sum1<=i+1&&j<=50;j++,sum1+=2,res1+=x[1]){
for(int k=0,sum2=sum1,res2=res1;res2<=t&&sum2<=i+1&&k<=33;k++,sum2+=3,res2+=x[2]){
for(int l=0,sum3=sum2,res3=res2;res3<=t&&sum3<=i+1&&l<=25;l++,sum3+=4,res3+=x[3]){
for(int m=0,sum4=sum3,res4=res3;res4<=t&&sum4<=i+1&&m<=20;m++,sum4+=5,res4+=x[4]){
dp[i][j][k][l][m]=dp[i-1][j][k][l][m];
if(j){
dp[i][j][k][l][m]=max(dp[i][j][k][l][m],dp[max(0ll,i-2)][j-1][k][l][m]+a[i]-a[i-1]);
}
if(k){
dp[i][j][k][l][m]=max(dp[i][j][k][l][m],dp[max(0ll,i-3)][j][k-1][l][m]+a[i]-a[i-2]);
}
if(l){
dp[i][j][k][l][m]=max(dp[i][j][k][l][m],dp[max(0ll,i-4)][j][k][l-1][m]+a[i]-a[i-3]);
}
if(m){
dp[i][j][k][l][m]=max(dp[i][j][k][l][m],dp[max(0ll,i-5)][j][k][l][m-1]+a[i]-a[i-4]);
}
ans=max(ans,dp[i][j][k][l][m]);
}
}
}
}
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
solve();
}
H.Step Debugging
题意:忘光了
题解:按照题意模拟dfs过程即可,我是被队友嘴巴操纵写的代码。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod=20220911;
string s;
int dfs(){
int res=0,w;
string s;
while(cin>>s){
if(s=="for")return res;
else if(s=="repeat"){
int now=dfs();
cin>>w>>s;
(res+=now*w)%=mod;
}
else if(s=="library")res=(res+1)%mod;
else if(s=="fin")return res;
}
return res;
}
signed main(){
ios_base::sync_with_stdio(false), cin.tie(0);
cout<<dfs()%mod;
}
J.Gachapon
题意:一个抽卡游戏,玩家想要抽中某张卡,一旦抽中立刻停止抽奖。
给定两个整数
X
,
Y
X,Y
X,Y 表示进行
X
X
X 轮抽卡,结束游戏的轮数期望是
Y
Y
Y 。你需要构造一组包含
X
X
X 个数的数组
p
p
p 。其中
p
i
p_i
pi 表示在第
i
i
i 轮游戏抽中卡的概率,满足
p
X
=
=
1
p_X==1
pX==1 且
∀
1
≤
i
<
x
,
0
<
p
i
<
1
\forall{1 \leq i < x},0< p_i < 1
∀1≤i<x,0<pi<1 。对于所有的
p
i
p_i
pi 以最简分数形式输出,保证输出的最简分数
a
/
b
a/b
a/b 满足
a
,
b
≤
10000
a,b \leq 10000
a,b≤10000。
令 q i q_i qi 表示进行 i i i 轮游戏结束的概率,考虑将题意用公式写出则有
∑
q
i
=
1
∑
i
∗
q
i
=
Y
\begin{aligned} \sum q_i &=1 \\ \sum i*q_i&=Y \end{aligned}
∑qi∑i∗qi=1=Y
我们需要构造序列
q
q
q 满足以上两个方程。假设所有
q
i
q_i
qi 的最简分数。令分母的最小公倍数为
n
n
n ,
a
i
a_i
ai 表示分母为
n
n
n 时候的
q
i
q_i
qi 的分子,则有,
s
u
m
i
sum_i
sumi 为
a
a
a 数组前
i
i
i 项和。对于任何一个合法的
q
q
q 序列,对应有
p
i
=
a
i
n
−
s
u
m
i
−
1
p_i = \frac{a_i}{n-sum_{i-1}}
pi=n−sumi−1ai,发现每一轮的
p
i
p_i
pi 的分母小于等于
n
n
n,为满足题意要求可令
n
=
10000
n=10000
n=10000,随后即有
∑
a
i
=
10000
∑
i
∗
a
i
=
10000
∗
Y
\begin{aligned} \sum a_i &=10000 \\ \sum i*a_i&=10000*Y \end{aligned}
∑ai∑i∗ai=10000=10000∗Y
考虑构造一个满足题意的解序列
a
a
a ,首先找到满足上式,对应期望最小值,则为
a
1
=
10001
−
x
a_1=10001-x
a1=10001−x, 其余为
1
1
1 ,然后在每次
a
i
−
1
−
1
,
a
i
+
1
a_{i-1}-1,a_{i}+1
ai−1−1,ai+1 过程中,期望增加一,循环即可找到一个解,随后输出即可。
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
int x,y,ex,p[1005];
void solve(){
cin>>x>>y;
ex=p[1]=10001-x;
for(int i=2;i<=x;i++){
p[i]=1;
ex+=i;
}
for(;ex<y*10000;){
for(int i=1;ex<y*10000&&i<x;i++){
p[i]--,p[i+1]++;
ex++;
}
}
for(int i=1,fenmu=10000;i<=x;i++){
int d=__gcd(p[i],fenmu);
cout<<p[i]/d<<" "<<fenmu/d<<endl;
fenmu-=p[i];
}
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
solve();
}
K.Pyramad
题意:玩家起始攻击力为 1 1 1 ,训练可增长 1 1 1 攻击力(后续称之训练效果)。每天玩家可以选择两种操作
使训练效果+1
训练一次
每天做完选择之后会面临一个血量为 x i x_i xi 的怪物,当且仅当你的攻击力大于等于怪物血量的时候才能杀死他。怪物共 n n n 个,求最多杀死多少个怪物。 ( 1 ≤ n , x i ≤ 5000 , ) (1 \leq n,x_i \leq 5000,) (1≤n,xi≤5000,)
题解:太晚了明天写详细的。先放个代码。
读完题目感觉无从下手,先列出题目相关的变量 :怪物的数量,玩家的攻击力,训练效果,胜利的次数,而且攻击力显然至多和血量为一个数量级。因为每天可以增加攻击力或让训练效果增强很容易想到一个
O
(
n
3
)
O(n^3)
O(n3) 的 dp ,即令
d
p
i
,
j
,
k
dp_{i,j,k}
dpi,j,k 表示经过
i
i
i 个怪物 ,攻击力为
j
j
j ,训练效果为
k
k
k 的最大胜利数量。这个递推可以
O
(
1
)
O(1)
O(1) 转移。
考虑优化。一个较为显然的优化是训练效果至多增强到
n
\sqrt n
n 级别所以该维可以只用前
n
\sqrt n
n 部分。
简略证明:这里本来有个证明但是我一下子不知道怎么写,大致思路是这里是个凸函数。后面想明白了再补。
考虑第二个优化。容易发现当前的 dp 式子的维度无法继续优化。但是根据上一个优化结论,我们可以知道,假设我们不考虑一开始的怪物,只需要最少只需要
142
142
142 天即可将攻击力超过
5000
5000
5000 ,所以我们发现失败的轮数至多是
142
142
142 。
考虑将失败轮数作为 dp 的转移,令
d
p
i
,
j
,
k
dp_{i,j,k}
dpi,j,k 表示经过
i
i
i 个怪物 ,失败次数为
j
j
j ,训练效果为
k
k
k 的最大攻击力。然后枚举每一天选择两种方案即可
O
(
1
)
O(1)
O(1) 转移。由于可能存在部分无效状态所以可以使用刷表法进行转移。最后对所有有效的状态求
M
A
X
(
i
−
j
)
MAX(i-j)
MAX(i−j) 即可。
#include <bits/stdc++.h>
using namespace std;
int n,k,ans;
int x[5005];
int dp[5005][150][75];//前i个输j把,w到k的最大攻击力
void solve(){
cin>>n;
memset(dp,-1,sizeof(dp));
for(int i=1;i<=n;i++){cin>>x[i];}
dp[0][0][1]=1;
for(int i=0;i<=n;i++){
for(int j=0;j<=142;j++){
for(int k=1;k<=73;k++){
if(dp[i][j][k]!=-1){
ans=max(ans,i-j);
if(dp[i][j][k]>=x[i+1]){
dp[i+1][j][k+1]=max(dp[i+1][j][k+1],dp[i][j][k]);
}
else{
dp[i+1][j+1][k+1]=max(dp[i+1][j+1][k+1],dp[i][j][k]);
}
if(dp[i][j][k]+k>=x[i+1]){
dp[i+1][j][k]=max(dp[i][j][k]+k,dp[i+1][j][k]);
}
else{
dp[i+1][j+1][k]=max(dp[i][j][k]+k,dp[i+1][j+1][k]);
}
}
}
}
}
cout<<ans;
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
solve();
}
L.LCS-like Problem
题意:给定两个字符串 s , t s,t s,t 求从 s s s 的最长子序列满足该子序列和 t t t 的最长公共子序列小于等于 1 1 1 。文章来源:https://uudwc.com/A/rZ3zD
题解:DP,对 s s s 中字母分出现在 t t t 中和没出现在 t t t 中考虑。对于没出现过的字母,贪心选择即可。对于出现在 t t t 中的字母,我们可以建立一个矩阵 c k i , j ck_{i,j} cki,j 表示字母 i i i 后面能否添加 j j j。然后直接 26 n 26n 26n 求最大值即可。文章来源地址https://uudwc.com/A/rZ3zD
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
string s,t;
int n,m;
int ck[30][30],tot[26];
int dp[30];
void solve(){
cin>>s>>t;
n=s.size();m=t.size();
s=" "+s;t=" "+t;
for(int i=m;i;i--){
int k=t[i]-'a';
for(int j=0;j<26;j++){
ck[k][j]=tot[j];
}
tot[k]++;
}
int res=0,ans=0;
for(int i=1;i<=n;i++){
int k=s[i]-'a';
if(tot[k]==0){
res++;continue;
}
if(!ck[k][k])dp[k]=dp[k]+1;
for(int j=0;j<26;j++){
if(j!=k&&ck[j][k]==0){
dp[k]=max(dp[k],dp[j]+1);
}
}
}
for(int i=0;i<26;i++){
ans=max(ans,dp[i]);
}
cout<<ans+res;
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
solve();
}