2025 端午集训 Day 2-3 阴间题选记
闲话
和 tbc 玩麦块,前后用了差不多 2h 就把龙打了。
然后是阴间的抢夺 -3,为什么锤 \(12\) 只小黑只有 \(3\) 个珍珠。。
我这个电脑居然能流畅带起来 1.20.1,肯定是优化 mod 太强了而不是麻将优化了。。
但是打开麦块之后才发现键盘的 Win 和 Alt 键位交换到正常了。
为什么键盘会把 Win 和左 Alt 时不时交换下位置。。。
cnblogs 阅读 2k 达成。
学习 xde 的 HN 口音。
[ABC292Ex] Rating Estimator
大于一个值之后都不会变这个条件很难受。。
想想怎么比大小来着
没错用作差法
我们给原数组一个前缀和,然后把分母移过去,不要出现小数不然又要被精度恶心,于是这个数组变成:
这个东西如果小于 \(0\) 意味着还没有大于那个给定的 Rating,如果大于零就是大于那个 Rating 了。
现在考虑怎么单点修,由于是前缀和于是只需要后缀修改一下就可以了。
但是区间覆盖似乎并不是很好做,因为只是单点修于是我们维护一个原始值的数组,每次查的时候算下变化量然后就变成区间加了。
还要找到第一个大于给定 Rating 的位置,这个也是好办的,在线段树上放个大于标记,在上面二分即可。
全局查询要把这个减去的 \(B\times k\) 加回来。
code
Show me the code
#define psb push_back
#define mkp make_pair
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
ll read(){
ll x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=5e5+5;
int sc[N],rv[N];
struct seg{
int l,r;
int maxx;
int v;int lzt;
int realv;
int maxpos;
int frontpos;
}t[N<<2];
int n,b,q;
void build(int p,int l,int r){
t[p].l=l;t[p].r=r;
t[p].lzt=0;
if(l==r){
t[p].v=sc[l];t[p].realv=rv[l];
t[p].maxx=t[p].v;t[p].maxpos=l;
if(t[p].maxx>0)t[p].frontpos=l;
else t[p].frontpos=0x3f3f3f3f;
return ;
}
int mid=l+r>>1;
build(p*2,l,mid);build(p*2+1,mid+1,r);
t[p].v=t[p*2].v+t[p*2+1].v;
if(t[p*2].maxx>t[p*2+1].maxx){
t[p].maxx=t[p*2].maxx;
t[p].maxpos=t[p*2].maxpos;
}
else{
t[p].maxx=t[p*2+1].maxx;
t[p].maxpos=t[p*2+1].maxpos;
}
t[p].frontpos=min(t[p*2].frontpos,t[p*2+1].frontpos);
return ;
}
int getd(int p,int pos,int k){
if(t[p].l==t[p].r&&t[p].l==pos){
int ddd=k-t[p].realv;
t[p].realv=k;
return ddd;
}
int mid=t[p].l+t[p].r>>1;
if(pos<=mid)return getd(p*2,pos,k);
if(mid<pos)return getd(p*2+1,pos,k);
}
void pushdown(int p){
if(t[p].lzt==0)return;
int k=t[p].lzt;t[p].lzt=0;
int ls=p*2,rs=p*2+1;
t[ls].v+=(t[ls].r-t[ls].l+1)*k;
t[ls].maxx+=k;
t[ls].lzt+=k;
if(t[ls].maxx>0){
t[ls].frontpos=min(t[ls].frontpos,t[ls].maxpos);
}
else t[ls].frontpos=0x3f3f3f3f;
t[rs].v+=(t[rs].r-t[rs].l+1)*k;
t[rs].maxx+=k;
t[rs].lzt+=k;
if(t[rs].maxx>0){
t[rs].frontpos=min(t[rs].frontpos,t[rs].maxpos);
}
else t[rs].frontpos=0x3f3f3f3f;
return ;
}
void upd(int p,int l,int r,int k){
if(l<=t[p].l&&t[p].r<=r){
t[p].maxx+=k;t[p].v+=(t[p].r-t[p].l+1)*k;
t[p].lzt+=k;
if(t[p].maxx>0){
t[p].frontpos=min(t[p].frontpos,t[p].maxpos);
}
else t[p].frontpos=0x3f3f3f3f;
return ;
}
pushdown(p);
int mid=t[p].l+t[p].r>>1;
if(l<=mid)upd(p*2,l,r,k);
if(mid<r)upd(p*2+1,l,r,k);
t[p].v=t[p*2].v+t[p*2+1].v;
if(t[p*2].maxx>t[p*2+1].maxx){
t[p].maxx=t[p*2].maxx;
t[p].maxpos=t[p*2].maxpos;
}
else{
t[p].maxx=t[p*2+1].maxx;
t[p].maxpos=t[p*2+1].maxpos;
}
t[p].frontpos=min(t[p*2].frontpos,t[p*2+1].frontpos);
return ;
}
int frontier(int p){
if(t[p].l==t[p].r&&t[p].maxx>=0)return t[p].l;
if(t[p].maxx>=0){
pushdown(p);
if(t[p*2].maxx>=0)return frontier(p*2);
else return frontier(p*2+1);
}
}
int query(int p,int l,int r){
if(l<=t[p].l&&t[p].r<=r){
return t[p].v;
}
pushdown(p);
int mid=t[p].l+t[p].r>>1;
int sub=0;
if(l<=mid)sub+=query(p*2,l,r);
if(mid<r)sub+=query(p*2+1,l,r);
return sub;
}
signed main(){
cin>>n>>b>>q;
ll delta=0;
for(int i=1;i<=n;i++){
cin>>sc[i];
rv[i]=sc[i];
delta+=i*b;
sc[i]+=sc[i-1];
}
for(int i=1;i<=n;i++){
sc[i]-=i*b;
}
build(1,1,n);
for(int i=1;i<=q;i++){
int c,x;cin>>c>>x;
int ddc=getd(1,c,x);
upd(1,c,n,ddc);
if(t[1].maxx>=0){
int gf=frontier(1);
cout<<fixed<<setprecision(12)<<(query(1,gf,gf)*1.0+(gf*b))/(1.0*gf)<<'\n';
}
else cout<<fixed<<setprecision(12)<<(query(1,n,n)*1.0+b*n*1.0)/(1.0*n)<<'\n';
}
return 0;
}
P2596 [ZJOI2006] 书架
应用下 pbds 即可。
code
Show me the code
#define psb push_back
#define mkp make_pair
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
using namespace __gnu_pbds;
using namespace std;
__gnu_pbds::tree<std::pair<int, int>, __gnu_pbds::null_type,
std::less<std::pair<int, int>>, __gnu_pbds::rb_tree_tag,
__gnu_pbds::tree_order_statistics_node_update>
tr;
typedef long long ll;
ll read(){
ll x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=1e5+5;
int h=1,t=10;
int prefer[N];
int main(){
int n,m;cin>>n>>m;
t=n;
for(int i=1;i<=n;i++){
int p;cin>>p;
tr.insert(mkp(i,p));
prefer[p]=i;
}
for(int i=1;i<=m;i++){
string op;cin>>op;
if(op=="Top"){
int s;cin>>s;
tr.erase(mkp(prefer[s],s));
prefer[s]=--h;
tr.insert(mkp(prefer[s],s));
}
if(op=="Bottom"){
int s;cin>>s;
tr.erase(mkp(prefer[s],s));
prefer[s]=++t;
tr.insert(mkp(prefer[s],s));
}
if(op=="Insert"){
int s,t;cin>>s>>t;
if(t==0)continue;
auto dlshl=tr.lower_bound(mkp(prefer[s],s));
if(t==1){
pair<int,int> p1=(*dlshl);
auto dlshl1=next(dlshl);
tr.erase(dlshl);
pair<int,int> p2=(*dlshl1);
tr.erase(dlshl1);
swap(prefer[p1.second],prefer[p2.second]);
swap(p1.first,p2.first);
tr.insert(p1);tr.insert(p2);
}
if(t==-1){
pair<int,int> p1=(*dlshl);
auto dlshl1=prev(dlshl);
tr.erase(dlshl);
pair<int,int> p2=(*dlshl1);
tr.erase(dlshl1);
swap(prefer[p1.second],prefer[p2.second]);
swap(p1.first,p2.first);
tr.insert(p1);tr.insert(p2);
}
}
if(op=="Ask"){
int s;cin>>s;
cout<<tr.order_of_key(mkp(prefer[s],s))<<'\n';
}
if(op=="Query"){
int s;cin>>s;s--;
cout<<(*tr.find_by_order(s)).second<<'\n';
}
}
return 0;
}
P2042 [NOI2005] 维护数列
平衡树维护区间问题。
最大子段和所使用的前缀和后缀最大和可以兼容交换区间这个操作。
调不过啊。。。
code
Show me the code
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
ll x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
#define NOC 73357733
const int N=1e5+5;
struct ftp{
int ls;
int rs;
int var;
unsigned int hvar;
int sizet;
bool lzt;
int v;
int qianz,houz;
int sum;
int cover;
}fhq[N];
int cnt=0,root;
mt19937 rnd(114514);
int x,y,z;
int newnode(int var){
fhq[++cnt].ls=0;
fhq[cnt].rs=0;
fhq[cnt].var=var;
fhq[cnt].hvar=rnd();
fhq[cnt].sizet=1;
fhq[cnt].cover=NOC;
fhq[cnt].sum=fhq[cnt].v=fhq[cnt].var;
fhq[cnt].qianz=fhq[cnt].houz=max(0,var);
return cnt;
}
void update(int p){
int ls=fhq[p].ls,rs=fhq[p].rs;
fhq[p].sizet=fhq[ls].sizet+fhq[rs].sizet+1;
fhq[p].qianz=max(max(fhq[ls].qianz,fhq[ls].v+fhq[p].var+fhq[rs].qianz),0);
fhq[p].houz=max(max(fhq[rs].houz,fhq[rs].v+fhq[p].var+fhq[ls].houz),0);
fhq[p].v=fhq[ls].v+fhq[rs].v+fhq[p].var;
fhq[p].sum=max(fhq[ls].houz+fhq[rs].qianz,0)+fhq[p].var;
if(ls)fhq[p].sum=max(fhq[p].sum,fhq[ls].sum);
if(rs)fhq[p].sum=max(fhq[p].sum,fhq[ls].sum);
}
void pushdown(int p){
if(fhq[p].lzt!=0){
swap(fhq[p].ls,fhq[p].rs);
swap(fhq[p].houz,fhq[p].qianz);
fhq[fhq[p].ls].lzt^=1;
fhq[fhq[p].rs].lzt^=1;
fhq[p].lzt=0;
}
if(fhq[p].cover!=NOC){
int ls=fhq[p].ls,rs=fhq[p].rs;
int var=fhq[p].cover;fhq[p].cover=NOC;
fhq[ls].cover=var;
fhq[ls].v=fhq[ls].sizet*var;
fhq[ls].sum=max(fhq[ls].v,var);
fhq[ls].qianz=fhq[ls].houz=max(0,fhq[ls].v);
fhq[ls].var=var;
fhq[rs].cover=var;
fhq[rs].v=fhq[rs].sizet*var;
fhq[rs].sum=max(fhq[rs].v,var);
fhq[rs].qianz=fhq[rs].houz=max(0,fhq[rs].v);
fhq[rs].var=var;
}
return ;
}
void split(int p,int siz,int &x,int &y){
if(!p){
x=y=0;
return;
}
pushdown(p);
if(fhq[fhq[p].ls].sizet<siz){
x=p;
split(fhq[p].rs,siz-fhq[fhq[p].ls].sizet-1,fhq[p].rs,y);
}
else{
y=p;
split(fhq[p].ls,siz,x,fhq[p].ls);
}
update(p);
return;
}
int merge(int x,int y){
if(!x||!y)return x+y;
if(fhq[x].hvar<fhq[y].hvar){
pushdown(x);
fhq[x].rs=merge(fhq[x].rs,y);
update(x);
return x;
}
else{
pushdown(y);
fhq[y].ls=merge(x,fhq[y].ls);
update(y);
return y;
}
}
void add(int l,int var){
split(root,l-1,x,y);
root=merge(merge(x,newnode(var)),y);
return;
}
void cov(int l,int r,int var){
split(root,l-1,x,y);
split(y,r-l+1,y,z);
fhq[y].cover=var;
fhq[y].v=(r-l+1)*var;
fhq[y].sum=max(fhq[y].v,var);
fhq[y].qianz=fhq[y].houz=max(0,fhq[y].v);
fhq[y].var=var;
root=merge(x,merge(y,z));
return ;
}
void rev(int l,int r){
split(root,l-1,x,y);
split(y,r-l+1,y,z);
fhq[y].lzt^=1;
root=merge(merge(x,y),z);
return;
}
ll sum(int l,int r){
split(root,l-1,x,y);
split(y,r-l+1,y,z);
ll ans=fhq[y].v;
root=merge(merge(x,y),z);
return ans;
}
void print(int p){
if(!p)return;
pushdown(p);
print(fhq[p].ls);
cout<<fhq[p].var<<' ';
print(fhq[p].rs);
}
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
int p;cin>>p;
root=merge(root,newnode(p));
}
for(int i=1;i<=m;i++){
string op;
cin>>op;
//cout<<op<<'\n';
if(op=="INSERT"){
int pos,tot;cin>>pos>>tot;
for(int i=1;i<=tot;i++){
int varr;cin>>varr;
split(root,pos-1,x,y);
root=merge(x,merge(newnode(varr),y));
}
}
if(op=="DELETE"){
int pos,tot;cin>>pos>>tot;
int l=pos,r=pos+tot-1;
split(root,l-1,x,y);
split(y,r-l+1,y,z);
root=merge(x,z);
}
if(op=="MAKE-SAME"){
int pos,tot,c;cin>>pos>>tot>>c;
int l=pos,r=pos+tot-1;
cov(l,r,c);
}
if(op=="REVERSE"){
int pos,tot;cin>>pos>>tot;
int l=pos,r=pos+tot-1;
rev(l,r);
}
if(op=="GET-SUM"){
int pos,tot;cin>>pos>>tot;
int l=pos,r=pos+tot-1;
cout<<sum(l,r)<<'\n';
}
if(op=="MAX-SUM"){
cout<<fhq[root].sum<<'\n';
}
}
return 0;
}
线段树分治
好东西啊。
很早之前想学了但是一直没去认真学。
按照时间构建线段树,把每个点的存在情况拆到 \(O(\log k)\) 个区间上。这个挺像标记永久化的。
o还要会用并查集判断二分图,扩域下即可。
o还要会撤销并查集。
撤销并查集就不能用路径压缩了,因为没法在合适的空间下记录之前的状态,为了并查集的时间复杂度,得用按秩合并,反正用了这个也是 log 的时间复杂度挺优秀的了。
然后的操作挺像那个难调的要死的P3250 [HNOI2016] 网络,但是那个题的时间是离散的,这个题的时间访问是连续的。
这样,我们取一路上来到的线段树上节点的边,放进并查集之前判一下有没有冲突,也就是连的这两个点的颜色必须不一样,如果有冲突那么这个线段树节点代表的大区间都没机会了直接无解。如果还有机会就放进并查集。
之后到叶子了就结算。
然后由于访问的时间是连续的,我们不需要每次都从线段树的树根走到叶子,这是线段树分治优化时间复杂度的地方。只需要可以撤销已经计算完的边即可。这个是可撤销并查集的事。
然后没什么了。。
code
Show me the code
#define psb push_back
#define mkp make_pair
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
ll read(){
ll x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=3e5+5;
struct edge{
int u,v,nxt;
}e[N*2];
int _head[N];int idx=-1;
void add(int u,int v){
idx++;
e[idx].u=u;e[idx].v=v;e[idx].nxt=_head[u];
_head[u]=idx;
return ;
}
int dfn[N],low[N],id,siz[N];
stack<int> s;
vector<int> col[N];
int vdcc=0;
void tarjan(int u,int fa){
dfn[u]=low[u]=++id;
s.push(u);
for(int i=_head[u];i!=-1;i=e[i].nxt){
int v=e[i].v;
if(fa==(i^1))continue;
if(!dfn[v]){
tarjan(v,i);
low[u]=min(low[u],low[v]);
if(dfn[u]<=low[v]){
vdcc++;
col[vdcc].push_back(u);
siz[vdcc]++;
while(s.top()!=v){
int vi=s.top();s.pop();
col[vdcc].push_back(vi);
siz[vdcc]++;
}
s.pop();col[vdcc].push_back(v);
siz[vdcc]++;
}
}
else low[u]=min(low[u],dfn[v]);
}
}
vector<int> ed[N];
int nw[N],sz[N];
ll ans=0;
int n,m;
int c[N],szm[N];
bool vis[N];
void dfs(int u,int fa,int cc){
bool inner=0;
c[u]=cc;
for(int v:ed[u]){
if(v==fa)continue;
inner=1;
dfs(v,u,cc);
sz[u]+=sz[v];
}
if(u<=n)sz[u]++;
return ;
}
void dfs1(int u,int fa,int tot){
if(u<=n)sz[u]++;
vis[u]=1;
for(int v:ed[u]){
if(v==fa)continue;
dfs1(v,u,tot);
ans+=sz[v]*sz[u]*2*nw[u];
sz[u]+=sz[v];
}
ans+=(tot-sz[u])*sz[u]*2*nw[u];
return ;
}
signed main(){
memset(_head,-1,sizeof _head);
cin>>n>>m;vdcc=n;
for(int i=1;i<=m;i++){
int u,v;
u=rd;v=rd;
add(u,v);add(v,u);
}
for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i,-1);
for(int i=n+1;i<=vdcc;i++){
for(int v:col[i]){ed[i].push_back(v);ed[v].push_back(i);}
}
for(int i=1;i<=vdcc;i++){
if(i<=n)nw[i]=-1;
else nw[i]=siz[i];
}
int cnt=0;
for(int i=n+1;i<=vdcc;i++){
if(!sz[i]){dfs(i,-1,++cnt);szm[cnt]=sz[i];}
}
memset(sz,0,sizeof sz);
for(int i=n+1;i<=vdcc;i++){
if(!vis[i]){dfs1(i,-1,szm[c[i]]);}
}
cout<<ans;
return 0;
}
P4514 上帝造题的七分钟
二维树状数组。
听起来挺高级?
只是给一维的树状数组暴力升维,也就是由一个一维数组上的 lowbit 变成了二维数组上的两个 lowbit。
这个东西依然可以维护前缀和,二维差分以后同样可以区域加区域求和。
啊呀为什么要有这种东西。。。一维的推公式已经很恶心了。。。
总之就是推完公式以后可以知道它要维护四个 \(\times 1 ,\times i ,\times i ,\times (i\times j)\) 的二维树状数组。
扯。。
code
Show me the code
#define psb push_back
#define mkp make_pair
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
//#define int long long
using namespace std;
typedef long long ll;
ll read(){
ll x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=2049;
char x;
int n,m;
int lowbit(int p){return p&(-p);}
long long tree4[N][N];
int tree2[N][N],tree3[N][N],tree1[N][N];
void upd(int l1,int l2,int delta){
for(int i=l1;i<=n;i+=lowbit(i)){
for(int j=l2;j<=m;j+=lowbit(j)){
tree1[i][j]+=delta;
tree2[i][j]+=delta*l1;
tree3[i][j]+=delta*l2;
tree4[i][j]+=delta*l1*l2;
}
}
}
ll pans(int l1,int l2){
ll ans=0;
for(int i=l1;i>0;i-=lowbit(i)){
for(int j=l2;j>0;j-=lowbit(j)){
ans+=1ll*(l1+1)*(l2+1)*tree1[i][j]-1ll*(l2+1)*tree2[i][j]-1ll*(l1+1)*tree3[i][j]+tree4[i][j];
}
}
return ans;
}
signed main(){
cin>>x>>n>>m;
char op;
while(cin>>op){
if(op=='L'){
int a,b,c,d,x;cin>>a>>b>>c>>d>>x;
upd(a,b,x);
upd(c+1,d+1,x);
upd(a,d+1,-x);
upd(c+1,b,-x);
}
else{
int a,b,c,d;cin>>a>>b>>c>>d;
cout<<pans(c,d)-pans(a-1,d)-pans(c,b-1)+pans(a-1,b-1)<<'\n';
}
}
return 0;
}
模拟赛 #1
因为挂得分比较少所以才 rk4 的。。。
T1 A. Alice 和璀璨花
给你两个数列 \(a,b\),长度为 \(n\),设其一个子序列 \(a'\) 长度为 \(k\),找出满足 \(\forall i\in[1,k],a'_{i+1} >b_ia'_i\) 的最长子序列。
\(n\le 10^6\)
首先有一个看起来很唐的 DP:
欸我草我草稿纸不见了,没关系那东西是个逆天的 \(O(n^3)\) 没蛋用。
然后会发现这个 DP 只和长度对应 \(a'_i \times b_i\) 的最小值有关系,于是可以变成 \(O(n^2)\)。
我当然在想那个 \(O(n \log n)\) 求 LIS 的板子,但是我寻思这 \(a'_i \times b_i\) 哪有单调性啊。
为什么没有单调性呢。。。。
于是唐没了 \(50\)pts。
但是发现这东西如果 \(b>1\) 那么增长会很快,然后可以限制答案长是 log 的,于是拿了这个场上只有我拿了的 \(15\)pts,因为大家都过了 T1。
code
Show me the code
#define psb push_back
#define mkp make_pair
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
ll x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=1e6+5;
ll a[N],b[N];
ll lenmin[N];
int main(){
freopen("alice.in","r",stdin);
freopen("alice.out","w",stdout);
int n;cin>>n;bool f1=0;
for(int i=1;i<=n;i++)a[i]=rd;
for(int j=1;j<=n;j++){b[j]=rd;f1|=b[j]>1?0:1;}
for(int i=0;i<=n;i++)lenmin[i]=LONG_LONG_MAX;
int l=1;lenmin[l]=a[1];
for(int i=2;i<=n;i++){
int wp=upper_bound(lenmin+1,lenmin+1+l,a[i])-lenmin;
if(lenmin[wp-1]*b[wp-1]<a[i]){
if(wp==1+l){l++;lenmin[l]=a[i];}
else lenmin[wp]=a[i];
}
}
cout<<l;
return 0;
}
这不就是板子吗。。。。。。。。
T2
但是数据范围是 \(n\le 10^6\)。
观察下会发现对于两个点一次只能用操作二让它们之间的距离减少奇数倍。
于是我们大力猜想这序列只能合并所有奇数位或者所有偶数位,而且我们还可以选择不合并某些奇数位或者偶数位。
贪心就行了。方案数是好算的。
我认为 A>B
。。。方案数一点都不好算,怎么挂了 5pts
end 以后发现 A 过的比 B 多。。
95 pts code
Show me the code
#define psb push_back
#define mkp make_pair
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef long long ll;
ll read(){
ll x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=1e6+5;
ll a[N];
vector<int> oi,ei;
signed main(){
freopen("bug.in","r",stdin);
freopen("bug.out","w",stdout);
int n;cin>>n;
int flag=0;
for(int i=1;i<=n;i++){a[i]=rd;a[i]>=0?flag=1:flag|=0;}
ll odd=0,even=0;
ll opl=n,opr=0,epl=n,epr=0;
if(!flag){
ll maxx=LONG_LONG_MIN;ll pos=0;
int st=0;
for(int i=1;i<=n;i++){
int rl=(i-1-1+1),rr=(n-(i+1)+1);
if(a[i]>maxx){
maxx=a[i];
if(rl==0)rl=-2;if(rr==0)rr=-2;
st=(rl/2)+1+(rr/2)+1;
}
else if(a[i]==maxx)st=min(st,(rl/2)+1+(rr/2)+1);
}
cout<<maxx<<'\n'<<st;
return 0;
}
int ost=0,est=0;
for(int i=1;i<=n;i+=2){
if(a[i]>0){
opl=min(opl,i);opr=max(opr,i);
odd+=a[i];oi.push_back(i);
}
}
for(int i=2;i<=n;i+=2){
if(a[i]>0){
epl=min(epl,i);epr=max(epr,i);
even+=a[i];ei.push_back(i);
}
}
if(odd>even){
cout<<odd<<'\n';
if(oi.size()>1){
int cur=1;
for(int i=oi[0]+2;i<=n;i+=2){
if(i!=oi[cur])ost++;
else cur++,ost++;
if(cur>=oi.size())break;
}
}
int rl=(opl-1-1+1),rr=(n-(opr+1)+1);
if(rl==0)rl=-2;if(rr==0)rr=-2;
ost+=(rl/2)+1+(rr/2)+1;
cout<<ost<<'\n';
return 0;
}
else if(odd==even){
cout<<odd<<'\n';
if(oi.size()>1){
int cur=1;
for(int i=oi[0]+2;i<=n;i+=2){
if(i!=oi[cur])ost++;
else cur++,ost++;
if(cur>=oi.size())break;
}
}
int rl=(opl-1-1+1),rr=(n-(opr+1)+1);
if(rl==0)rl=-2;if(rr==0)rr=-2;
ost+=(rl/2)+1+(rr/2)+1;
if(ei.size()>1){
int cur=1;
for(int i=ei[0]+2;i<=n;i+=2){
if(i!=ei[cur])est++;
else cur++,est++;
if(cur>=ei.size())break;
}
}
rl=(epl-1-1+1),rr=(n-(epr+1)+1);
if(rl==0)rl=-2;if(rr==0)rr=-2;
est+=(rl/2)+1+(rr/2)+1;
cout<<min(est,ost)<<'\n';
}
else{
cout<<even<<'\n';
if(ei.size()>1){
int cur=1;
for(int i=ei[0]+2;i<=n;i+=2){
if(i!=ei[cur])est++;
else cur++,est++;
if(cur>=ei.size())break;
}
}
int rl=(epl-1-1+1),rr=(n-(epr+1)+1);
if(rl==0)rl=-2;if(rr==0)rr=-2;
est+=(rl/2)+1+(rr/2)+1;
cout<<est<<'\n';
return 0;
}
return 0;
}
T3
神仙题。
给两个单调不降序列 \(a,b\),可以每次任选一个 \(i\),给 \(a_i + k\),\(k\) 可正可负,代价是 \(k^2\),最多只能改 \(m\) 次。而且每次修改完要保证 \(a\) 单调不降,问你最小操作代价或报告无解。
首先有:
计算所有 \(i\) 对应的 \(d_i=|a_i - b_i|\),我们要做的就是安排至多 \(m\) 次操作让 \(d_i\) 全是 \(0\)。
如果 \(d_i\) 中不为 \(0\) 的数量为 \(k\),那么如果 \(m\ge k\) 肯定有解。因为对于 每次修改完要保证 \(a\) 单调不降 这个条件。我们显然是可以重排我们的操作序列让他从高位向低位调整来满足的。
接下来考虑我们开头的这个不等式,意思就是我们把 \(d_i\) 分成尽量小的份去修改肯定是更优的,但是我们只有 \(m\) 次机会去把这些份修改。
而且我们不能分成浮点数去修改,所以不能直接用这个不等式的右式计算代价。
现在先想想我们怎么计算把一个 \(d_i\) 拆成 \(k\) 份去修改的最小代价。
记 \(z =\left \lfloor \dfrac{d_i}{k} \right \rfloor\),\(v=d_i \bmod k\)
现在拆出来的 \(d_i\) 的序列应该是 \(k\) 个 \(z\) 和一个 \(v\)。这个原因应该显然。
然后我们既然想要尽量平均,就要把一些 \(v\) 均摊给 \(z\) 变成 \(z+1\)。
然后手摸一下你就可以写出这个计算代价的函数:
ll delta(ll d, ll k) {
ll z, v;
z = d / k, v = d - z * k;
return v * (z + 1) * (z + 1) + (k - v) * z * z;
}
然后这个题神仙的地方来了,考虑我们怎么表示出把 \(d_i\) 多分一份加进去的代价变化。
我们设把 \(x\) 拆成 \(k\) 份去修改的最小代价为 \(f_{x,k}\),特殊的令 \(f_{x,0}=0\),然后显然有:
然后还有这个东西:
打个表看看就知道了,虽然我不会证。
于是 \(f_{x,k+1} - f_{x,k}\) 这个东西是有单调性的,我们设其为 \(g_{x,k}\)。
对于所有的 \(d_i\),我们先应用一遍 \(f_{d_i,1}\) 并把代价和加入答案中,表示不拆分直接放进去,这会消耗我们 \(n\) 次机会。
之后把所有的 \(g_{d_i,2}\) 放到一个优先队列(升序排序)里,现在考虑取出队顶的元素并加入答案中意味着什么。
由我们的定义,对 \(d_i\) 的操作会变成 \(f_{d_i,2}\),\(f_{d_i,1}\) 自己减没了。
也就是说这个操作代表我们决策把 \(d_i\) 多分一份加进去。
而且我们是升序排序,此时答案变小的一定是最多的。由 \(g\) 的单调性可知,这个决策是最优的。
那么,我们再把 \(g_{d_i,3}\) 的值放进去,参加下次决策即可。
会发现我们此时至多做这次操作 \(n-m\) 次,于是模拟下这个过程,这题就做完了。
很有意思!
但是代码调了半个下午。。
code
Show me the code
#define psb push_back
#define mkp make_pair
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
ll read(){
ll x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=2e5+5;
const ll mod=998244353;
ll a[N],b[N];
ll delta(ll x, ll y) {
ll z = (x + y - 1) / y;
ll v = z * y - x;
return (z - 1) * (z - 1) * v + (y - v) * z * z;
}
ll d[N],divi[N];
struct work{
ll var;
int id;
bool operator<(const work &x)const{
return x.var>var;
}
};
signed main(){
freopen("a.in", "r", stdin), freopen("a.out", "w", stdout);
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)a[i]=rd;
for(int i=1;i<=n;i++)b[i]=rd;
for (int i=1;i<=n;i++)d[i]=abs(a[i] - b[i]);
sort(d+1,d+n+1,greater<int>());
while (n&&d[n]==0)--n;
if(!n) {
cout<<0;
return 0;
}
if(n>m) {
cout<<-1;
return 0;
}
ll ans=0;
priority_queue<work> q;
for(int i=1;i<=n;i++){
m--;divi[i]=1;
q.push(work{delta(d[i],divi[i])-delta(d[i],divi[i]+1),i});
(ans+=delta(d[i],divi[i])%mod)%=mod;
}
while(m--){
int id=q.top().id;ll var=q.top().var;
q.pop();
(ans+=mod-var%mod)%=mod;
++divi[id];
q.push(work{delta(d[id],divi[id])-delta(d[id],divi[id]+1),id});
}
cout<<ans;
return 0;
}
T4
让你用非负整数构造一个长度为 \(n\) 的字符串,有 \(m\) 条限制,形如从 \(i,j\) 开始的两个子串,它们的最长公共前缀必须长度为 \(k\),求满足要求的字典序最小的字符串。
\(n,m \le 1000\)。
为什么要把这个东西扔到 T4 去。放在 T1 我就会了。
不是我在说什么。。。。。。
首先 \(k=0\) 代表不能有 LCP,于是给这两个位置连边表示不能相同。
注意连位置的时候要从前往后连,因为我们要字典序最小。
如果所有限制全是 \(0\) 那跑个拓扑排序就完事了。然后你就有 70pts。
然后如果 \(k>0\) 意思是对应位置的点必须相同,于是把这些点用并查集缩起来。
但是结尾后一个位置两个地方必须不同,于是再连个边就行。
不是为什么场上连这个都想不到。。。
code
Show me the code
#define psb push_back
#define mkp make_pair
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
ll x=0,f=1;
char c=getchar();
while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
const int N=2005;
int col[N];
struct constr{
int u,v,d;
}c[N];
vector<int> e[N];
int inn[N];
int mp[N];
bool mex[N][N];
int ans[N];
int reff[N];
int fa[N];
int _find(int u){return fa[u]==u?u:fa[u]=_find(fa[u]);}
int main(){
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
int n,m;cin>>n>>m;
for(int i=1;i<=n;i++)col[i]=i,fa[i]=i;
int mit=m;
for(int i=1;i<=m;i++){
int x,y,z;cin>>x>>y>>z;
c[i].u=x;c[i].v=y;c[i].d=z;
if(z!=0){
for(int k=0;k<z;k++){
int p1=x+k,p2=y+k;
int t1=_find(p1),t2=_find(p2);
fa[t1]=t2;
//col[p1]=col[p2]=min(col[p1],col[p2]);
}
if(y+z<=n&&x+z<=n){mit++;c[mit].u=x+z;c[mit].v=y+z;c[mit].d=0;}
}
}
int cnt=1;
for(int i=1;i<=n;i++){
col[i]=_find(i);
if(i==1||(col[i-1]!=col[i]&&!mp[col[i]])){
reff[cnt]=col[i],mp[col[i]]=cnt++;
}
}
for(int i=1;i<=mit;i++){
int x=c[i].u,y=c[i].v,z=c[i].d;
if(z==0){
if(mp[col[x]]==mp[col[y]]){
cout<< -1;
return 0;
}
else{
if(mp[col[x]]>mp[col[y]])swap(x,y);
e[mp[col[x]]].push_back(mp[col[y]]);
inn[mp[col[y]]]++;
}
}
}
queue<int> q;
for(int i=1;i<=cnt;i++){
if(inn[i]==0)q.push(i);
}
while(q.size()){
int u=q.front();q.pop();
for(int i=0;i<=n;i++){
if(!mex[u][i]){ans[u]=i;break;}
}
for(auto v:e[u]){
mex[v][ans[u]]=1;
inn[v]--;
if(inn[v]==0)q.push(v);
}
}
for(int i=1;i<=n;i++){
cout<<ans[mp[col[i]]]<<' ';
}
return 0;
}
终于写完了。。

浙公网安备 33010602011771号