QZS8.18
咳咳。。。这场考试摸了
T1蓝蓝的棋盘
我菜炸了。。。
这竟然是个dp。。。长得真像博弈论
80分做法:设dp[x]表示棋子目前在位置x的答案(先手减去后手的分数).
状态转移方程dp[xl=max(a[y]-dp[y])(x<y<=min(n,x+m)),且dp[n]=0
直接枚举y转移即可,复杂度O(nm)
100分做法: 单调队列优化 复杂度O(n)
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
inline long long read(){
long long x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
int n;
long long l,r;
long long f[500005],m,a[500005],s[500005],q[500005];
int main() {
n=read();m=read();
for(int i=1;i<=n;i++)
a[i]=read();
l=1,r=1;
f[n]=0;q[1]=n;
for(int i=n-1;i>=0;i--) {
while(l<=r&&q[l]>i+m) l++;
f[i]=a[q[l]]-f[q[l]];
while(l<=r&&(a[q[r]]-f[q[r]])<=(a[i]-f[i])) r--;
q[++r]=i;
}
printf("%lld\n",f[0]);
return 0;
}
T2淘淘的集合
操作1:启发式合并,就把小的集合(vector维护)暴力拆开,合并到大集合上
操作2:每个集合维护个tag标记,然后直接在标记上加O(1)
以上两个操作不是直接做的,要记录下来等询问的时候再做
操作3:清空,我们记录一个清空时间,这里可以用分块开个数组,然后某区间赋值为时间
操作4:查询,把区间查询化为单点查询
我们现在就是要用个数据结构,支持区间赋值,单点查询——线段树
查询这段清空时间pretime,用前缀和相减的方法a[nowtime]-a[pretime]
复杂度迷人。。。
具体的代码+注释见下
#include <iostream>
#include <vector>
#include <cstdio>
#include <cstring>
using namespace std;
#define ll long long
#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((l+r)>>1)
inline ll read(){
ll x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
const ll N=4e6+5;
ll n,m,tot,a[N];
ll ans[N];
ll fa[N],set_tag[N];//每个集合的tag
inline ll find(ll x) {
return x==fa[x]?x:fa[x]=find(fa[x]);
}
struct question {
bool add;
ll id,pos;
question () {}
question (bool add_,ll id_,ll pos_):add(add_),id(id_),pos(pos_){}
};
vector<ll>v[N];//集合
vector<question>ask[N];//询问4
struct option{
ll id,x,y;
option() {}
option(ll id_,ll x_,ll y_):id(id_),x(x_),y(y_){}
}op[N];
ll tag[N],tim[N];
void build(ll l,ll r,ll p){
tag[p]=-1;//tag必须赋值-1,因为要和时间为0的区别开
if(l==r) return;
build(l,mid,ls);
build(mid+1,r,rs);
}
inline void pushdown(ll p) {
if(tag[p]==-1) return;
tag[ls]=tag[rs]=tag[p];
tag[p]=-1;
}
void change(ll l,ll r,ll L,ll R,ll v,ll p) {
if(l>R||r<L) return;
if(L<=l&&r<=R) {tag[p]=v;return;}
pushdown(p);
change(l,mid,L,R,v,ls);
change(mid+1,r,L,R,v,rs);
}
void query(ll l,ll r,ll L,ll R,ll p) {
if(l>R||r<L) return;
if(L<=l&&r<=R&&tag[p]!=-1) {//注意!!!tag为-1说明这个区间的点没改过,time为0就不用赋成tag了
for(ll i=l;i<=r;i++)
tim[i]=tag[p];
return;
}
pushdown(p);
query(l,mid,L,R,ls);
query(mid+1,r,L,R,rs);
}
void merge(ll x,ll y) {
if(x==y) return;
if(v[x].size()<v[y].size()) swap(x,y);
for(ll i=0;i<v[y].size();i++) {
ll now=v[y][i];
a[now]+=set_tag[y]-set_tag[x];//把y合并到x上后,y的标记会变成x的标记,为使其值不变,所以要进行这步
v[x].push_back(now);
}
fa[find(y)]=find(x);
}
inline ll get(ll x){
return a[x]+set_tag[find(x)];
}
int main() {
n=read();m=read();
for(ll i=1;i<=n;i++)
fa[i]=i,v[i].push_back(i);
build(1,n,1);
for(ll i=1,c,l,r;i<=m;i++) {
c=read();l=read();r=read();
if(c==1||c==2) {
op[i]=option(c,l,r);//操作1,2先离线下来,等有4的时候再用
} else if(c==3) {
change(1,n,l,r,i,1);//区间赋值,线段树就是维护 time
} else {
++tot;//询问编号
query(1,n,l,r,1);//这里复杂度迷人。。。O(n)?
for(ll j=l;j<=r;j++) {//把区间查询改成单点查询
ask[tim[j]].push_back(question(0,tot,j));
ask[i].push_back(question(1,tot,j));
}
}
}
//注意没修改的时间为0,所以从0开始
for(ll i=0;i<=m;i++) {
ll fx=find(op[i].x);
if(op[i].id==1) merge(fx,find(op[i].y));
if(op[i].id==2) set_tag[fx]+=op[i].y;
for(ll j=0;j<ask[i].size();j++)
if(ask[i][j].add) ans[ask[i][j].id]+=get(ask[i][j].pos);
else ans[ask[i][j].id]-=get(ask[i][j].pos);
}
for(ll i=1;i<=tot;i++)
printf("%lld\n",ans[i]);
return 0;
}
T3
数学公式
O(1)时间内算出1−n四次方和的公式
https://www.cnblogs.com/BlogOfchc1234567890/p/9863162.html
9,10好像是单调栈
具体转化就是把一段为1的数搞成个矩形。。。然后啥最大矩形面积...gugugu