youyou的垃圾桶
1.题意简述
现在有n个敌人,第i个敌人的初始攻击力为正整数ai。初始生命值为正整数W。定义如下流程为一场战斗:从第1个敌人开始,每个敌人依次循环进行攻击。第i个敌人发起攻击时,生命值W减去ai,同时ai翻倍。当W≤0时,本场战斗立刻结束。然后重置生命值W以及所有敌人的攻击力ai。定义本次战斗的评分为接受敌人攻击的次数(不包括致命攻击)。q次询问,每次询问给出三个数l,r,d,表示对第[l,r]个敌人进行强化,使每个敌人的ai增加d,然后立刻进行一场战斗。输出此次战斗的评分。询问之间相互影响。
2.大体思路
设所有垃圾桶当前攻击力总和为S。
考虑暴力,因为生命值W≤1018,攻击力是翻倍递增的,因此最多只会打logW轮。因此暴力的时间复杂度是O(nqlogW)。可以获得20分。假设这场战斗完整地打了k轮,那么这k轮需要消耗的生命值为S×(2^0+ 2^1+···+ (2k^−1)) =S×((2^k)−1)。即要求出最大的m,使得∑m i=1ai≤W−S×(2k−1)。答案即为k×n+m。
显然,对于每一次修改,S只会增加(ri−li+ 1)×di。对于每一个询问,我们需要求出最大的k。发现k不需要每次都枚举,因为每次操作后,答案只会变小,也就是k是递减的。对于相同的k,显然m也在递减。于是用指针维护m的值,同时用两个差分数组维护区间加即可。对于不同的k,暴力求解即可。
cpp
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll read(){
ll X = 0,r = 1;
char ch = getchar();
while(!isdigit(ch) && ch != '-') ch = getchar();
if(ch == '-') r = -1,ch = getchar();
while(isdigit(ch)) X = X*10+ch-'0',ch = getchar();
return X*r;
}
const int N = 2e5+10;
int n,q;
ll W,a[N],t[N<<2],tag[N<<2];
#define ls rt<<1
#define rs rt<<1|1
inline void push_up(int rt){
t[rt] = t[ls]+t[rs];
}
void build(int rt,int l,int r){
if(l == r){
t[rt] = a[l];
return ;
}
int mid = (l+r) >> 1;
build(ls,l,mid);
build(rs,mid+1,r);
push_up(rt);
}
inline void change(int rt,int l,int r,ll d){
t[rt] += (r-l+1)*d;
tag[rt] += d;
}
inline void push_down(int rt,int l,int r){
if(tag[rt]){
int mid = (l+r) >> 1;
change(ls,l,mid,tag[rt]);
change(rs,mid+1,r,tag[rt]);
tag[rt] = 0;
}
}
void update(int rt,int l,int r,int ql,int qr,ll d){
if(ql <= l && r <= qr) return change(rt,l,r,d);
push_down(rt,l,r);
int mid = (l+r) >> 1;
if(ql <= mid) update(ls,l,mid,ql,qr,d);
if(qr > mid) update(rs,mid+1,r,ql,qr,d);
push_up(rt);
}
int query(int rt,int l,int r,ll now,ll bas){
if(l == r) return l-1;
push_down(rt,l,r);
int mid = (l+r) >> 1;
if(bas*t[ls] < now) return query(rs,mid+1,r,now-bas*t[ls],bas);
return query(ls,l,mid,now,bas);
}
void solve(){
ll sum = t[1],ans = 0,now = W,bas = 1;
while(now > sum){
now -= sum;
ans += n;
sum *= 2;
bas *= 2;
}
ans += query(1,1,n,now,bas);
cout << ans << "\n";
}
int main(){
n = read(); q = read(); W = read();
for(int i=1;i<=n;i++) a[i] = read();
build(1,1,n);
while(q--){
int l = read(),r = read();
ll d = read();
update(1,1,n,l,r,d);
solve();
}
return 0;
}
youyou不喜欢夏天
1.考虑youyou选出的连通块左右端点被确定为l,r。显然,全黑列他会都去选择,全白列他只会选择一个格子,因为这些不受yy的影响。考虑一黑一白的列。假如他两个格子都选择,那么贡献为0,如果只选择一个黑色的格子,虽然贡献是1,但是可能被yy操作变成−1。于是他有两种策略:所有的一黑一白列我们都选择两个,这样yy没办法操作。将x个一黑一白列选择一个格子,其余选择两个。这样yy可以操作。
2.发现若youyou选择策略二为优,当且仅当至少有2m个一黑一白列他选择了一个格子。否则,我们可以将这些列选择两个格子,显然连通块仍连通,对答案的贡献为0;而原来对答案的贡献为x−2m<0。因此,youyou的策略二,可以视作在不考虑操作的情况下选出一个连通块。我们只需求这个连通块的最大权值最后减去2m,这一部分可以用dp实现。youyou的策略一是经典最大子段和问题,也可以使用dp实现。
cpp
#include<bits/stdc++.h>
#define ffor(i,a,b) for(int i=(a);i<=(b);i++)
#define roff(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
const int MAXN=2e6+10;
int c,T,n,m,a[3][MAXN],dp[MAXN][4];
int main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>c>>T;
while(T--) {
cin>>n>>m;
string S,T;
cin>>S>>T;
ffor(i,1,n) a[1][i]=S[i-1]-'0';
ffor(i,1,n) a[2][i]=T[i-1]-'0';
ffor(i,0,3) dp[0][i]=-0x3f3f3f3f;
int ans=0;
ffor(i,1,n) {
int ad[4];
ad[0]=0,ad[1]=2*a[1][i]-1,ad[2]=2*a[2][i]-1,ad[3]=2*a[1][i]+2*a[2][i]-2;
if(a[1][i]!=a[2][i]) ad[1]=ad[2]=-0x3f3f3f3f;
ffor(j,0,3) dp[i][j]=ad[j];
ffor(k,0,3) ffor(j,0,3) if(j&k) dp[i][j]=max(dp[i][j],dp[i-1][k]+ad[j]);
ffor(j,0,3) ans=max(ans,dp[i][j]);
}
ffor(i,1,n) {
int ad[4];
ad[0]=0,ad[1]=2*a[1][i]-1,ad[2]=2*a[2][i]-1,ad[3]=2*a[1][i]+2*a[2][i]-2;
ffor(j,0,3) dp[i][j]=ad[j];
ffor(k,0,3) ffor(j,0,3) if(j&k) dp[i][j]=max(dp[i][j],dp[i-1][k]+ad[j]);
ffor(j,0,3) ans=max(ans,dp[i][j]-2*m);
}
cout<<ans<<'\n';
}
return 0;
}
youyou的序列II
1.首先进行特判,如果询问的区间中含有大于w1的数字,那么youyou显然必败。因为所有数均为非负整数,发现yy选择长度正好为c2的子区间是一定不劣的。以下称长度为c2,总和大于w2的子区间为"合法区间"。也即yy可以操作的区间。重要结论1:对于序列中不在任何一个"合法区间"内的数,一定不会对答案产生影响。对于这些数字,youyou可以用任意多次回合将它们染红,且yy无法重新将它们染蓝。而由于回合数是可视作无限的,youyou总有时间去将这些数字染色。
性质1:对于不存在任何一个"合法区间"的序列,youyou显然必胜。
性质2:存在"合法区间",若youyou可以一次性染红所有未染红的合法区间,则youyou必胜。
性质3:存在"合法区间",若youyou不可以做到一次性染红所有未染红的合法区间,则yy必胜。
40分:暴力求值 70分:暴力更新所有修改时受影响的区间 100分:线段树上二分求出l,r。
cpp
#include<bits/stdc++.h>
#define int long long
#define ffor(i,a,b) for(int i=(a);i<=(b);i++)
#define roff(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
const int MAXN=3e5+10;
int n,q,c1,c2,w1,w2,pre[MAXN],a[MAXN],opt[MAXN],x[MAXN],y[MAXN];
int lim[MAXN];
vector<int> upd[MAXN];
#define lson (k<<1)
#define rson (k<<1|1)
#define mid (l+r>>1)
int sum[MAXN<<2],mx[MAXN<<2];
void update_s(int k,int l,int r,int pos,int v) {
if(l==r) return sum[k]+=v,void();
if(pos<=mid) update_s(lson,l,mid,pos,v);
else update_s(rson,mid+1,r,pos,v);
return sum[k]=sum[lson]+sum[rson],void();
}
void update_m(int k,int l,int r,int pos,int v) {
if(l==r) return mx[k]+=v,void();
if(pos<=mid) update_m(lson,l,mid,pos,v);
else update_m(rson,mid+1,r,pos,v);
return mx[k]=max(mx[lson],mx[rson]),void();
}
int bfind(int k,int l,int r,int lim) { //找到第一个 > lim 的位置
if(sum[k]<=lim) return r+1;
if(l==r) return l;
if(sum[lson]>lim) return bfind(lson,l,mid,lim);
return bfind(rson,mid+1,r,lim-sum[lson]);
}
struct INFO {int sum,mx;};
INFO operator +(INFO A,INFO B) {return {A.sum+B.sum,max(A.mx,B.mx)};}
INFO query(int k,int l,int r,int x,int y) {
if(x<=l&&r<=y) return {sum[k],mx[k]};
if(y<=mid) return query(lson,l,mid,x,y);
if(x>mid) return query(rson,mid+1,r,x,y);
return query(lson,l,mid,x,y)+query(rson,mid+1,r,x,y);
}
set<int> st;
vector<int> psl[MAXN];
signed main() {
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>q>>c1>>c2>>w1>>w2;
c1=min(c1,n),c2=min(c2,n);
ffor(i,1,n) cin>>a[i],pre[i]=pre[i-1]+a[i];
ffor(i,1,q) cin>>opt[i]>>x[i]>>y[i];
ffor(i,1,q) if(opt[i]==1) upd[x[i]].push_back(i);
ffor(i,1,c2) update_s(1,1,q,1,a[i]);
ffor(i,1,c2) for(auto t:upd[i]) update_s(1,1,q,t,y[t]);
ffor(i,1,n-c2+1) {
lim[i]=bfind(1,1,q,w2);
for(auto t:upd[i]) update_s(1,1,q,t,-y[t]);
update_s(1,1,q,1,a[i+c2]-a[i]);
for(auto t:upd[i+c2]) update_s(1,1,q,t,y[t]);
psl[lim[i]].push_back(i);
}
memset(sum,0,sizeof(sum));
ffor(i,1,n) update_s(1,1,n,i,a[i]),update_m(1,1,n,i,a[i]);
ffor(i,1,q) {
for(auto id:psl[i]) st.insert(id);
if(opt[i]==2) {
if(y[i]-x[i]+1<c2) {
auto info=query(1,1,n,x[i],y[i]);
int flg=1;
if(info.mx>w1) flg=0;
if(info.sum>w2) {
if(info.sum>w1||c1<y[i]-x[i]+1) flg=0;
}
if(flg) cout<<"cont\n";
else cout<<"tetris\n";
}
else {
int flg=1,l=x[i],r=y[i]-c2+1;
auto it1=st.lower_bound(l),it2=st.upper_bound(r);
if(it1!=it2) {
it2--;
int L=*it1,R=*it2;
auto info=query(1,1,n,L,R+c2-1);
if(c1<R+c2-1-L+1||info.sum>w1) flg=0;
}
auto info=query(1,1,n,x[i],y[i]);
if(info.mx>w1) flg=0;
if(flg) cout<<"cont\n";
else cout<<"tetris\n";
}
}
else update_s(1,1,n,x[i],y[i]),update_m(1,1,n,x[i],y[i]);
}
return 0;
}
youyou的三进制数
1.注意到3×10^5的三进制表示只有12位。考虑建图,发现题目所述与圆方树的性质很像,用tarjan跑出圆方树。圆方树最初是处理"仙人掌图"(每条边在不超过一个简单环中的无向图)的一种工具,不过发掘它的更多性质,有时我们可以在一般无向图上使用它。
2.维护的信息:
对于每个点,我们维护以下三个信息:
sumt表示若有一条以节点t为结尾的序列c,它的总贡献。
ssumt表示对于以节点t为根的整棵子树的总贡献。
sumsont表示对于节点t的所有儿子,它们的ssum之和。维护sumson数组是容易的,考虑如何维护sum数组和ssum数组。
3.由于这棵树的高度较低(本题中不会超过23),考虑对每一个圆点暴力往上跳,计算答案。对于当前跳到的点,我们考虑当前点的贡献和兄弟节点的贡献。有了预处理好的数组,这部分的计算就容易了。