前言
由于太菜了,多校26 只改出来了 T1 ,于是直接并在一起写啦~~~。
T0 NOIP 2018
解题思路
第一次考场上面写三分,然而我并不知道三分无法处理不是严格单峰的情况,但凡有一个平台都不行??
我们的贪心策略一定是尽量让我们选择的两个物品的值尽量接近,二分最后选择的一个价值,然后判断是否可行。
需要特判一下只选择一个的情况,以及两个个数相差一的情况,实现细节有一点多。。
code
#include<bits/stdc++.h>
#define int __int128
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
inline void write(int x)
{
#define sta number
int sta[70],top=0; if(x<0) putchar('-'),x=~(x-1);
while(x) sta[++top]=x%10,x/=10; if(!top) sta[++top]=0;
while(top) putchar(sta[top--]+'0'); putchar('\n');
#undef sta
}
int q,n,a,b,c,d,ans;
inline int g(int x,int y,int pos){return x*pos+y*(pos-1)*pos/2;}
inline long long g2(long long x,long long y,long long pos){return x*pos+y*(pos-1)*pos/2;}
inline int work(int x,int y){return (x-a)/b+(y-c)/d+2;}
bool check(int x)
{
int t1=max((int)0,(x-a)/b+1),t2=max((int)0,(x-c)/d+1),temp=g(a,b,t1)+g(c,d,t2);
if(temp<=n) return true;
if(t1>=1&&g(a,b,t1-1)+g(c,d,t2)<=n) ans=max(ans,t1+t2-1);
else if(t2>=1&&g(a,b,t1)+g(c,d,t2-1)<=n) ans=max(ans,t1+t2-1);
return false;
}
void sol(int x,int y)
{
int l=0,r=n,temp=-1;
while(l<=r)
{
int mid=(l+r)>>1,jud=false;
jud|=x*mid>n; jud|=g(x,y,mid)!=g2(x,y,mid);
jud|=g(x,y,mid)>n;
if(!jud) l=mid+1,temp=mid;
else r=mid-1;
}
ans=max(ans,temp);
}
void solve()
{
a=read(); b=read(); c=read(); d=read(); n=read();
int l=0,r=n,temp=-1; ans=0; sol(a,b); sol(c,d);
while(l<=r)
{
int mid=(l+r)>>1;
if(check(mid)) l=mid+1,temp=mid;
else r=mid-1;
}
if(~temp) ans=max(ans,work(temp,temp)); write(ans);
}
#undef int
int main()
{
#define int long long
freopen("money.in","r",stdin); freopen("money.out","w",stdout);
q=read(); while(q--) solve();
return 0;
}
T1 开挂
解题思路
一个直接的想法就是给 \(b\) 排序然后给需要移动步数多的点较小的 \(b\) 。
我们肯定是要把一些重复的值填在一些空闲的值域里面,那么为了让答案尽量小,我们要让移动步数尽量不平均。
于是可以先把所有需要安排位置的数字放入一个栈,数值从栈低到栈顶单调不降。
对于一个空闲的值域,优先选择栈顶元素也就是较大的元素放入就好了..
code
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=1e6+10,INF=1e18;
int n,p,cnt,all,top,sta[N],a[N],b[N],c[N],s[N];
pair<int,int> res[N];
ull ans;
#undef int
int main()
{
#define int long long
freopen("openhook.in","r",stdin); freopen("openhook.out","w",stdout);
n=read(); a[n+1]=INF;
for(int i=1;i<=n;i++) a[i]=read(); sort(a+1,a+n+1);
for(int i=1;i<=n;i++) b[i]=read(); sort(b+1,b+n+1);
for(int i=1,len=1;i<=n;i++)
if(a[i]==a[i+1]) s[++all]=a[i];
else if(a[i+1]!=a[i]+1)res[++cnt]=make_pair(a[i]+1,a[i+1]-1);
for(int i=1,pos=0;i<=cnt;i++)
{
if(s[pos]>res[i].second) continue;
while(pos<all&&s[pos+1]<res[i].first) sta[++top]=s[++pos];
for(int j=res[i].first;j<=res[i].second&⊤j++)
{
c[++p]=j-sta[top--];
while(pos<all&&s[pos+1]<j+1) sta[++top]=s[++pos];
}
}
sort(c+1,c+n+1,greater<int>());
for(int i=1;i<=n;i++) ans+=c[i]*b[i];
printf("%llu",ans);
return 0;
}
T2 叁仟柒佰万
解题思路
子段中 \(mex\) 的值一定是全局的 \(mex\) 值。
设 \(f_i\) 表示前 \(i\) 的数字划分子段 \(mex\) 等于 \(1\sim n\) 的 \(mex\) 值的方案数。
假设全局 \(mex\) 值为 \(K\) ,那么转移方程就是 \(f_i=\sum\limits_{j=1}^{i-1}f_{j-1}\times[mex_{[i,j]}=K]\)
我们可以维护一个指针 \(pos\) 表示 \([1,pos]\) 的 \(mex\) 值都是 \(K\) 这个指针显然只会右移,同时在维护一下当前 \([pos,i]\) 区间的 \(mex\) 值用来维护。
实现细节稍多。
code
#include<bits/stdc++.h>
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=37e6+10,Num=262143,mod=1e9+7;
int T,n,pos,num,ans,X,Y,s[N],pre[N],cnt[N],mex;
bool vis[N];
void solve()
{
n=read(); ans=vis[0]=0; pos=num=mex=0; pre[0]=1; s[0]=N-1;
if(n!=N-10) for(int i=1;i<=n;i++) s[i]=read(),vis[i]=false;
else{X=read();Y=read();for(int i=2;i<=n;i++)s[i]=(1ll*s[i-1]*X+Y+i)&Num;}
for(int i=1;i<=n;i++) vis[s[i]]=true; while(vis[mex]) mex++;
if(n!=N-10) for(int i=0;i<=n;i++) cnt[i]=0;
for(int i=1;i<=n;i++)
{
int las=-1; cnt[s[i]]++; while(cnt[num]) num++;
while(num>=mex&&pos<=i){cnt[s[pos]]--; if(!cnt[s[pos]]&&s[pos]<num) las=num,num=s[pos]; pos++;}
if(~las) pos--,cnt[s[pos]]++,num=las; pre[i]=pre[i-1]; if(pos>=1) pre[i]=(pre[i]+pre[pos-1])%mod;
}
printf("%d\n",(pre[n]-pre[n-1]+mod)%mod);
}
int main()
{
freopen("clods.in","r",stdin); freopen("clods.out","w",stdout);
T=read(); while(T--) solve();
return 0;
}
T3 超级加倍
解题思路
有一种 Kruskal 重构树的感觉(好像从严格意义上来讲并不是),更多的开始能使一种笛卡尔树的东西吧。。
按照编号从大到小建树为 T1 从小到大建树为 T2 。
那么这两棵树的性质就是: 满足任意两点 x, y 在 T1 中的 lca 是路径最小值,在 T2 中是路径最大值。
那么符合条件的点对 \((x,y)\) 就需要满足 x 在 T1 中是 y 的祖先,y 在 T2 中是 x 的祖先。
直接在一棵树的 DFS 序上开树状数组,然后 DFS 另一棵树记录祖先的信息查询就好了。
code
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=2e6+10;
int n,dfn[N],siz[N];
long long ans;
vector<int> v[N];
struct Edge
{
int tot=1,tim,head[N],ver[N<<1],nxt[N<<1];
void add(int x,int y){ver[++tot]=y;nxt[tot]=head[x];head[x]=tot;}
void dfs(int x){dfn[x]=++tim;siz[x]=1;for(int i=head[x];i;i=nxt[i])dfs(ver[i]),siz[x]+=siz[ver[i]];}
}e1,e2;
struct DSU
{
int fa[N];
void init(){for(int i=1;i<=n;i++)fa[i]=i;}
int find(int x){if(fa[x]==x) return x;return fa[x]=find(fa[x]);}
void merge(int x,int y){fa[find(x)]=find(y);}
}D1,D2;
struct BIT
{
int tre[N];
#define lowbit(x) (x&(-x))
void insert(int x,int val){for(int i=x;i<=n;i+=lowbit(i))tre[i]+=val;}
int query(int x){int sum=0;for(int i=x;i;i-=lowbit(i))sum+=tre[i];return sum;}
int query(int l,int r){return query(r)-query(l-1);}
}T;
void dfs(int x)
{
ans+=T.query(dfn[x],dfn[x]+siz[x]-1); T.insert(dfn[x],1);
for(int i=e2.head[x];i;i=e2.nxt[i]) dfs(e2.ver[i]);
T.insert(dfn[x],-1);
}
int main()
{
freopen("charity.in","r",stdin); freopen("charity.out","w",stdout);
n=read(); read(); D1.init(); D2.init();
for(int i=2,x;i<=n;i++) x=read(),v[x].push_back(i),v[i].push_back(x);
for(int i=n;i>=1;i--) for(auto it:v[i]) if(it>i) e1.add(i,D1.find(it)),D1.merge(it,i);
for(int i=1;i<=n;i++) for(auto it:v[i]) if(it<i) e2.add(i,D2.find(it)),D2.merge(it,i);
e1.dfs(1); dfs(n); printf("%lld",ans);
return 0;
}
T4 欢乐豆
解题思路
正解是 线段树+最短路 ,但是貌似可以乱搞过。。
先把所有的关于 \(m\) 条边所涉及到的点记录下来,那么别的点到其他点的距离就是初始权值。
同时我们还需要再记录一下初始权值最小的点以保证我们再后面的最短路中对于附给大边权的情况可以跑回来。
然后对于每一个记录下来的点为源点跑最短路,一边跑一遍用并茶几维护,具体实现可以再向优先队列里面加入元素的时候记录一个 bool 值。
表示时候已经计算过了这个点到别的联通块的距离,就可以直接跑了。。。
code
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=1e5+10,INF=1e18;
int n,m,mn=INF,mnid,ans,cnt,dis[N],sta[N],id[N],s[N],fa[N];
bool vis[N];
struct Node
{
int pos,dat,can;
bool friend operator < (Node x,Node y){return x.dat>y.dat;}
};
vector< pair<int,int> > v[N];
bool comp(Node x,Node y){return x.dat<y.dat;}
priority_queue<Node> q;
int find(int x){if(fa[x]==x) return x; return fa[x]=find(fa[x]);}
void New(int x){if(id[x]) return ; sta[++cnt]=x; id[x]=cnt;}
void insert(int x,int val)
{
if(fa[x]!=x) return ; fa[x]=x+1; dis[x]=val;
q.push((Node){x,dis[x]+s[sta[x]],true});
for(auto it:v[x]) if(dis[it.first]>dis[x]+it.second)
q.push((Node){it.first,dis[it.first]=dis[x]+it.second});
}
void solve(int fro)
{
for(int i=1;i<=cnt+1;i++) dis[i]=INF,fa[i]=i;
dis[fro]=0; q.push((Node){fro,0,false});
while(!q.empty())
{
Node temp=q.top(); int x=temp.pos; q.pop();
if(!temp.can){insert(x,temp.dat);continue;}
for(auto it:v[x]) vis[it.first]=true;
for(int i=find(1);i<=cnt;i=find(i+1)) if(!vis[i]) insert(i,temp.dat);
for(auto it:v[x]) vis[it.first]=false;
}
}
#undef int
int main()
{
#define int long long
freopen("happybean.in","r",stdin); freopen("happybean.out","w",stdout);
n=read(); m=read(); for(int i=1;i<=n;i++) s[i]=read();
for(int i=1,x,y,val;i<=m;i++)
x=read(),y=read(),val=read(),New(x),New(y),
v[id[x]].push_back(make_pair(id[y],val));
for(int i=1;i<=n;i++) if(!id[i]&&s[i]<mn) mn=s[i],mnid=i; if(mn!=INF) New(mnid);
for(int i=1;i<=n;i++) if(!id[i]) ans+=(n-1)*s[i];
for(int i=1;i<=cnt;i++)
{
int minn=INF; solve(i);
for(int j=1;j<=cnt;j++) ans+=dis[j],minn=min(minn,dis[j]+s[sta[j]]);
ans+=(n-cnt)*minn;
}
printf("%lld",ans);
return 0;
}