Noip模拟94 2021.11.10
话说这几天状态都不是很好,不知道为啥,今天更是获得了近期最低的成绩
还是要努力呀,太菜了,没几天就\(Noip\)了,这样下去可是不行啊
人均切掉的贪心题做了两个半小时都没做出来,非常不应该
\(UPD2021.11.11\)发现博客太水过意不去了,于是来完善一下
T1 开挂
贪心,发现考场上对一道题的第一感觉是很重要的,第一步对题意的转化和理解是十分关键的,
考后问别人题意的转化都是看每一个点走到单独的一个位置上所用的最短距离
而我考场上转化的就非常奇怪,这也是考场上想了多种假的贪心前提条件吧。。。
这可能就是\(BOBO\)说的不顺手吗??不懂
那么我们把题意转化为每个点走到独一无二的位置上所花费的最小步数
那么我们先看在花费相同的时候怎么做
应该是记录一个\(cnt\)表示还有几个数没有放到位置上,那么再记录一个桶\(c_x\)表示值为\(x\)的数的个数
那么枚举值,转移就是\(cnt+=c_x,cnt=\max(cnt-1,0),ans+=cnt*b_1\)表示当前值的点入队,留下一个点在这个值,剩下的点向前走
那么考虑正解,就把刚才的变量换成一个栈,每个留下点的操作就相当于弹栈,弹出最大的值留下来更优
那么就需要考码力了
openhook
#include<bits/stdc++.h>
typedef unsigned long long ULL;
#define int long long
using namespace std;
const int NN=1e6+1;
namespace AE86{
FILE *wsn;
#define gc() p1==p2 and(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++
char buf[1<<20],*p1=buf,*p2=buf;
auto read=[](){
int x=0,f=1;char ch=gc();while(ch<'0'||ch>'9')ch=gc();
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=gc();}
return x*f;
};
auto write=[](ULL x,char opt='\n'){
char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
for(short i=len-1;i>=0;--i){putchar(ch[i]);}putchar(opt);
};
}using namespace AE86;
int n,a[NN],b[NN]; ULL ans;
map<int,int> s;
int stk[NN],top;
int now,cnt,tmp;
int pos[NN];
auto cmp=[](int a,int b){return a>b;};
namespace WSN{
inline short main(){
wsn=freopen("openhook.in","r",stdin);
wsn=freopen("openhook.out","w",stdout);
n=read();
for(int i=1;i<=n;++i)a[i]=read(),++s[a[i]];
for(int i=1;i<=n;++i)b[i]=read();
sort(a+1,a+n+1); sort(b+1,b+n+1);
now=a[1]; int tot=n;
while(tot>0||top){
int num=s[now];
for(int i=1;i<=num;++i)
stk[++top]=now,--s[now],--tot;
s.erase(s.find(now));
if(top>0){
pos[++tmp]=now-stk[top--];
++now;
}else if(tot>0) now=s.begin()->first;
}
sort(pos+1,pos+tmp+1,cmp);
for(int i=1;i<=tmp;++i)ans+=pos[i]*b[i];
write(ans);
return 0;
}
}
signed main(){return WSN::main();}
T2 三千七百万
不用繁体因为现在是二十一世纪
正解\(dp\),因为有单调性拿一个双指针+桶维护就可以做到均摊\(O(1)\)转移
不难发现他的每一个合法的分配相等的那个\(mex\)值都是一样的,且都等于全局的\(mex\)
那么我们只需要求出全局的\(mex\)就知道什么时候是一组合法分配
然后对于\(mex\)的增长是具有单调性的,那么使用指针就可以做到均摊\(O(1)\)转移了
colds
#include<bits/stdc++.h>
using namespace std;
const int NN=37000001,mod=1e9+7;
namespace AE86{
FILE *wsn;
#define gc() p1==p2 and (p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++
char buf[1<<20],*p1=buf,*p2=buf;
auto read=[](){
int x=0,f=1;char ch=gc();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=gc();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=gc();} return x*f;
};
auto write=[](int x,char opt='\n'){
char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
for(short i=len-1;i>=0;--i){putchar(ch[i]);}putchar(opt);
};
}using namespace AE86;
int T,n,a[NN],x,y,mex,dp[NN];
int vis[NN];
auto solve=[](){
n=read(); mex=0;
if(n!=37000000){
memset(vis,0,sizeof(int)*(n+2));
for(int i=1;i<=n;i++)a[i]=read(),++vis[a[i]],dp[i]=0;
}
else{
x=read();y=read();a[1]=0; ++vis[0];
for(int i=2;i<=n;i++)a[i]=((a[i-1]*x+y+i)&262143),++vis[a[i]];
}
while(vis[mex])++mex;
memset(vis,0,sizeof(int)*(mex));
for(int i=mex+1;i<=n;i++)vis[i]=n+1;
dp[0]=1; int l=1,now=0;
for(int i=1;i<=n;i++){
if(!vis[a[i]]) ++now;
++vis[a[i]];
if(now==mex){
for(;(l^i)&&vis[a[l]]>1;l++) --vis[a[l]];
dp[i]=dp[i-1]+dp[l-1]; dp[i]%=mod;
}else dp[i]=dp[i-1];
}
write((dp[n]-dp[n-1]+mod)%mod);
};
namespace WSN{
inline short main(){
wsn=freopen("clods.in","r",stdin);
wsn=freopen("clods.out","w",stdout);
T=read(); while(T--)solve();
return 0;
}
}
signed main(){return WSN::main();}
T3 超级加倍
对于最大值和最小值分别建立重构树,这样再在两个树上进行\(dfs\)利用树状数组维护从一个点到根的链上的合法段条数就行了
那么为什么建\(kruskal\)重构树呢?
其实我也没学过,不过发现建出来的树有以下特性:
每条路径的实质没有变化,且每一层的点编号逐层递增
那么这样就非常方便统计最大值最小值了,这样就解释清楚为什么建最大和最小的重构树了
接下来的\(dp\)就是上面说的意思,一次\(dfs\)统计每一条链,然后另一遍\(dfs\)统计答案即可
charity
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int NN=1e7+5;
namespace AE86{
FILE *wsn;
#define gc() p1==p2 and (p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++
char buf[1<<20],*p1=buf,*p2=buf;
auto read=[](){
int x=0,f=1;char ch=gc();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=gc();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=gc();} return x*f;
};
auto write=[](int x,char opt='\n'){
char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
for(short i=len-1;i>=0;--i){putchar(ch[i]);}putchar(opt);
};
}using namespace AE86;
int n,ans;
#define star(e,i,x) for(int i=e.head[x],y=e.to[i];i;i=e.next[i],y=e.to[i])
struct SNOW{
int to[NN<<1],next[NN<<1],head[NN],rp;
inline void add(int x,int y){
to[++rp]=y; next[rp]=head[x]; head[x]=rp;
to[++rp]=x; next[rp]=head[y]; head[y]=rp;
}
}e,mx,mn;
namespace DSU{
int fa[NN];
inline int getfa(int x){return fa[x]=(x==fa[x])?x:getfa(fa[x]);}
auto merge=[](int x,int y){x=getfa(x);y=getfa(y);if(x==y)return;fa[y]=x;};
inline void build(){
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=n;i++)
star(e,j,i) if(y<i) mx.add(i,getfa(y)),merge(i,y);
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=n;i;i--)
star(e,j,i) if(y>i) mn.add(i,getfa(y)),merge(i,y);
}
}
struct BIT{
int c[NN];
inline void update(int x,int v){++x;while(x<NN)c[x]+=v,x+=(x&(-x));}
inline int query(int x,int a=0){++x;while(x)a+=c[x],x-=(x&(-x));return a;}
inline int calc(int l,int r){return query(r)-query(l-1);}
}tr;
int st[NN],cnt,ed[NN];
inline void dfs1(int f,int x){
st[x]=++cnt;
star(mn,i,x) if(y!=f) dfs1(x,y);
ed[x]=cnt;
}
inline void dfs2(int f,int x){
ans+=tr.calc(st[x],ed[x]);
tr.update(st[x],1);
star(mx,i,x) if(y!=f) dfs2(x,y);
tr.update(st[x],-1);
}
namespace WSN{
inline short main(){
wsn=freopen("charity.in","r",stdin);
wsn=freopen("charity.out","w",stdout);
n=read(); read();
for(int i=2,x;i<=n;i++)x=read(),e.add(x,i);
DSU::build(); dfs1(0,1); dfs2(0,n);
write(ans);
return 0;
}
}
signed main(){return WSN::main();}
T4 欢乐豆
\(Yubai\)卡评测姬了!!还没有开\(freopen\),非常欢乐
先去给\(XIN\)过生日了,再见。。。。
\(\textit{1 day later...}\)
回来了,这题非常\(nb\),题意可以转化为只有\(n\leq 6000,m\leq 3000\)的有向图
剩下的点之间如果没有连边就默认有一条\(a_x\)的边,然后统计全源最短路
那么我们把修改的边的两个端点放入一个联通块,其他的不在任何一个联通块内的点的\(ans=a_x\times(n-1)\)
考虑联通块内的点,需要魔改或者模拟\(dijkstra\)的过程,模拟的话需要用数据结构优化
我是用的线段树,每次把更新过的点的权值变成\(inf\),知道所有的点都是\(inf\)停止,然后\(pair\)记录最小值及其位置
打个标记记录有没有更新,等等操作可以看代码
然后联通块内的点的最短路就可以求出了,那么他对于联通块外的点就需要找到最短的一条出去的路,然后加上出去的边权,再乘上联通块外的点的个数就是贡献了
happybean
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int NN=1e5+5,MM=3005;
namespace AE86{
FILE *wsn;
#define gc() p1==p2 and (p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++
char buf[1<<20],*p1=buf,*p2=buf;
auto read=[](){
int x=0,f=1;char ch=gc();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=gc();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=gc();} return x*f;
};
auto write=[](int x,char opt='\n'){
char ch[20];short len=0;if(x<0)x=~x+1,putchar('-');
do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x);
for(short i=len-1;i>=0;--i){putchar(ch[i]);}putchar(opt);
};
}using namespace AE86;
typedef pair<int,int> PII;
#define pb push_back
#define mp make_pair
#define fi first
#define se second
int n,m,a[NN],ans;
multiset<int> s;
vector<PII>e[NN];
namespace DSU{
int fa[NN],siz[NN],kuai,id[NN];
inline int getfa(int x){return fa[x]=(fa[x]==x)?x:getfa(fa[x]);}
inline void merge(int x,int y){x=getfa(x),y=getfa(y);if(x==y) return;fa[y]=x; siz[x]+=siz[y];}
}using namespace DSU;
vector<int>p[MM];
const int inf=1e18;
struct SNOWtree{
#define lid (id<<1)
#define rid (id<<1|1)
PII mn[MM<<2];int ll[MM<<2],rr[MM<<2],laz[MM<<2];bool tag[MM<<2];
inline void pushup(int id){
if(ll[id]==rr[id]) return;
mn[id].fi=min(mn[lid].fi,mn[rid].fi);
if(mn[id].fi==mn[lid].fi&&!tag[lid]) mn[id].se=mn[lid].se;
else mn[id].se=mn[rid].se;
tag[id]=tag[lid]&tag[rid];
}
inline void pushdown(int id){
if(laz[id]==inf) return;
if(!tag[lid]) mn[lid].fi=min(mn[lid].fi,laz[id]),laz[lid]=min(laz[lid],laz[id]);
if(!tag[rid]) mn[rid].fi=min(mn[rid].fi,laz[id]),laz[rid]=min(laz[rid],laz[id]);
laz[id]=inf;
}
inline void build(int id,int l,int r){
ll[id]=l;rr[id]=r;laz[id]=inf;tag[id]=0;if(l==r)return mn[id]=mp(inf,l),void();
int mid=l+r>>1; build(lid,l,mid); build(rid,mid+1,r); pushup(id);
}
inline void modify(int id,int pos){
if(ll[id]==rr[id])return mn[id].fi=inf,tag[id]=1,void(); pushdown(id);
int mid=ll[id]+rr[id]>>1;if(pos<=mid) modify(lid,pos); else modify(rid,pos); pushup(id);
}
inline void update(int id,int l,int r,int v){
if(tag[id])return;if(l<=ll[id]&&rr[id]<=r)return mn[id].fi=min(mn[id].fi,v),laz[id]=min(laz[id],v),void();
pushdown(id);int mid=ll[id]+rr[id]>>1;if(l<=mid)update(lid,l,r,v);if(r>mid)update(rid,l,r,v); pushup(id);
}
}tr;
int in[NN],dis[MM];
auto solve=[](int i){
int sz=p[i].size(),tmp;
for(int j=0;j<sz;j++)in[p[i][j]]=j+1;
for(auto x:p[i]) s.erase(s.find(a[x]));
if(s.size()) tmp=*s.begin();else tmp=inf;
for(auto x:p[i]){
tr.build(1,1,sz); tr.update(1,in[x],in[x],0); dis[in[x]]=0;
while(tr.mn[1].fi!=inf){
int x=tr.mn[1].se,pre=1;
dis[x]=tr.mn[1].fi;
int d=dis[x];
tr.modify(1,x);
x=p[i][x-1];
for(auto y:e[x]){
tr.update(1,pre,in[y.fi]-1,d+a[x]);
tr.update(1,in[y.fi],in[y.fi],d+y.se);
pre=in[y.fi]+1;
}
tr.update(1,pre,sz,d+a[x]);
}
// exit(0);
int mn=inf;
for(int j=1;j<=sz;j++)ans+=min(dis[j],a[x]+tmp);
for(int j=1;j<=sz;j++)mn=min(mn,(dis[j]+a[p[i][j-1]])*(n-sz));
ans+=mn;
}
for(auto x:p[i]) s.insert(a[x]);
};
namespace WSN{
inline short main(){
wsn=freopen("happybean.in","r",stdin);
wsn=freopen("happybean.out","w",stdout);
n=read(); m=read();
for(int i=1;i<=n;i++)a[i]=read(),fa[i]=i,siz[i]=1,s.insert(a[i]);
for(int i=1,x,y,z;i<=m;i++)x=read(),y=read(),z=read(),merge(x,y),e[x].pb(mp(y,z));
for(int i=1;i<=n;i++)sort(e[i].begin(),e[i].end());
for(int i=1;i<=n;i++)if(getfa(i)==i&&siz[i]>1)id[getfa(i)]=++kuai;
for(int i=1;i<=n;i++)
if(siz[getfa(i)]>1)p[id[getfa(i)]].pb(i);
else ans+=a[i]*(n-1);
for(int i=1;i<=kuai;i++)solve(i);
// return 0;
write(ans);
return 0;
}
}
signed main(){return WSN::main();}
总算写的像样了,能说的过去了。。。