51nod模拟4/CSP-S模拟3
作者:互联网
T1 score and rank
%%t%ourist
比较智慧的贪心。考虑类似求最大子段和类的东西,我们逐个i考虑,当$a_i <0 $时,以i结尾的最大子段显然不会再超过S,但是它会抵消掉一部分之前的数
点击查看代码
#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i)
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define pli pair<long long ,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;
const int maxn=1e6+10,INF=1e9+7;
const ll LINF=1e15;
int a[maxn];
ll S,pre[maxn];
int n,ans;
multiset<ll>mS;
ll sum;
void solve(){
cin>>n>>S;
Rep(i,1,n)cin>>a[i],pre[i]=pre[i-1]+a[i];
if(S<0){
int res=0;
Rep(i,1,n)res+=(a[i]>=S);
return cout<<res<<"\n",void();
}
Rep(i,1,n){
int x=a[i];
if(x>0){
mS.insert(x);sum+=x;
while(sum>=S && (!mS.empty())){
auto it = prev(mS.end());
sum-=*it;
mS.erase(it);
++ans;
}
}
if(x<0){
if(sum+x>0){
sum+=x;
while(x<0 && (!mS.empty())){
auto it = mS.begin();
x+=*it;
mS.erase(it);
}
mS.insert(x);
}else mS.clear(),sum=0;
}
}
cout<<ans<<"\n";
}
int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }
T2 HZOI大作战
做法比较显然,发现每个询问都是从v到u做一次单调栈,于是想到可撤销栈,实际上就是倍增,处理出每个点向上走,交换了\(2^i\)次后所在的点,询问就是二分找到第一个大于初始值的位置,然后再实现一个查询从u到v的交换次数即可。
点击查看代码
#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i)
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;
#define gc if(++ip==ie)fread(ip=buf,1,SZ,stdin)
const int SZ=1<<21;char buf[SZ],*ie=buf+SZ,*ip=ie-1;
inline int read(){ gc;while(*ip<'-')gc; bool f=*ip=='-';if(f)gc; int x=*ip&15;gc; while(*ip>'-'){x*=10;x+=*ip&15;gc;} return f ? -x : x; }
const int maxn=5e5+10,maxm=1e6+10,INF=1e9;
int fa[maxn][19],n,ans[maxn],f[maxn],q;
struct Graph{
struct eg{int from,to,next;}e[maxm];
int len,head[maxn];int dep[maxn],w[maxn];
void lqx(int from,int to)
{ e[++len].from=from,e[len].to=to,e[len].next=head[from],head[from]=len; }
void Predfs(int u){
dep[u]=dep[f[u]]+1;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;if(v==f[u])continue;
f[v]=u;Predfs(v);
}
}
void Dfs(int u){
int x=f[u];
Dwn(i,18,0){
if(w[u]>=w[fa[x][i]]){ x=fa[fa[x][i]][0]; }
}
if(w[u]>=w[x])x=fa[x][0];
fa[u][0]=x;
for(int j=1;j<=18;++j)fa[u][j]=fa[fa[u][j-1]][j-1];
for(int i=head[u];i;i=e[i].next)if(e[i].to!=f[u])Dfs(e[i].to);
}
int Calc(int u,int v){
int ans=0;
Dwn(i,18,0){
if(dep[fa[u][i]]>=dep[v]){
u=fa[u][i];ans+=(1<<i);
}
}
return ans;
}
void Sol(int u,int v,int val){
int ans=0;
if(val<w[u])return cout<<Calc(u,v)+1<<"\n",void();
Dwn(i,18,0){
if(dep[fa[u][i]]>=dep[v] && w[fa[u][i]]<=val){
u=fa[u][i];
}
}
u=fa[u][0];
return cout<<Calc(u,v)+(dep[u]>=dep[v])<<"\n",void();
}
}G;
void solve(){
cin>>n>>q;Rep(i,1,n)cin>>G.w[i];G.w[0]=INF;
int x,y,z;Rep(i,2,n)cin>>x>>y,G.lqx(x,y),G.lqx(y,x);
G.Predfs(1);
G.Dfs(1);
while(q--){
cin>>x>>y>>z;
G.Sol(x,y,z);
}
}
int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }
T3 Delov的旅行
这题数据是我造的,于是在应验题组统一题面风格的要求下改了题面。
如果毒瘤数据给您带来了较差的游戏体验请来问候凉心造数据人
对数据的一些解释
- 由于验题组赛时几乎没人写这题,于是只有一份贪心做法,我只能对着它卡,甚至我不知道如何构造卡贪心的数据。
- 所有数据都是随机生成的
- 由于难以构造数据,为了卡掉可能出现的奇奇怪怪的做法,我只能稍加测试数据量并设置subtask保证不会有人乱搞拿到不该拿的分,为真正在努力做题的人提供良好的区分度
- 对于大样例,所有大样例假贪心都能过,
我故意的,提醒大家不要太相信大样例 - 每一个subtask都有卡贪心的点,
甚至小数据都有 - 由于数据随机,较小常数的正确做法应该是可以跑进两百毫秒以内的,为了提升大家的游戏体验,我没有调小时限来再卡掉一些并不太正确的做法,同时也避免时限造成的对正解复杂度分析的误导。当然,我简单构造了数据来卡掉某种错误做法。
- 如果你通过某种乱搞拿到了二十分以上的分数,请来与我交流
下面是题解
题意比较清楚,就是每天都要到一个叶子,最后一天回到1,要让每天的通行费的最大值最小。
Subtask1(5pts)
显然最大的通行费一定是从1号点左子树的一个叶子节点到右子树的一个叶子节点时产生的,直接算就行。属于是送分点
Subtask2(15pts)
叶子节点数最大为8,于是直接全排列枚举访问顺序即可,由于每条边只能走两次的限制,所以每次走进一棵子树则必定把这棵子树的所有叶子都访问完再出去,需要考虑一下访问顺序是否合法。这个也是送分点
到此为大众分二十分。
Subtask3(20pts)
这一部分是给暴力dp的分数,如果想到的话已经接近正解了。
考虑二分答案转化为判定。
我们发现,对于一棵子树来说,它的每种访问方案的信息,只有第一次进入,和最后一次返回根的两条路径长度有用,因为中间两个叶子节点产生的费用如果合法那么就是合法了之后也不会用到,只有进和出会在后边用到,一种贪心的思想就是让入和出尽量小,但是这是一个二维的偏序,显然贪心是有问题的,于是我们把贪心做法卡掉了。
设\(f[u][a][b]\)为以u为根的子树内,u到访问的第一个叶子节点的路径长度为a(即进子树),u到访问的最后一个叶子节点的路径长度为b(即出子树),且中间访问过程产生的最大费用不超过mid是否可行。转移是很显然的。考虑在一个节点是先进左子树还是先进右子树,把中间跨越子树时的那条路径Check一下是否大于mid即可,注意我们是不需要Check a和b是否合法的,因为第一天和最后一天不需要付钱。
在数据限制下,n最大为63,a,b最大为一百左右,我没试但是应该没啥问题,如果你写对了但没拿到的话请来问候凉心造数据人。
Subtask4(20pts)
这一部分是给大概\(n^2\)的做法的,但是我也不知道\(n^2\)能怎么做,也许你们有比较优秀的做法,原题里有这个部分分于是我就加上了。
Subtask5(40pts)
正解部分。发现在暴力dp时是有很多无用状态的,有大量根本不存在的状态,也有很多一定劣的状态,于是我们考虑每个点维护一个集合,存所有可能贡献的状态。集合里的状态一定是在a严格单增的同时b严格单减的。在合并两棵子树转移时,一个状态在另一棵子树上能匹配的状态一定是其集合的一个前缀,并且取a最大的那个一定最优,因为此时b最小。具有单调性于是可以双指针扫一遍转移。每个点的集合大小感性上看最劣都是\(O(n)\)的,但是这个上界远远达不到,甚至在随机数据下就是一个很小的常数,上界可能是log级别的,我们并不知道如何把状态数卡到上界,于是只能缩小时限,并且卡掉一些维护得不太严格的做法。
点击查看代码
#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".ans","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i)
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
#define int ll
using namespace std;
const int maxn=(1<<17)+10;
int n;
struct Ver{
ll a,b;
bool operator<(const Ver &rhs)const{
if(a!=rhs.a)return a<rhs.a;
return b>rhs.b;
}
};
set<Ver>S[maxn];
ll Lim;
bool flag;
struct Graph{
struct eg{int from,to,next,dis;}e[maxn*2];
int len,head[maxn];int w[maxn],fa[maxn],dfn[maxn];
vector<pii>son[maxn];
void lqx(int from,int to,int dis)
{ e[++len].from=from,e[len].to=to,e[len].next=head[from],e[len].dis=dis,head[from]=len; }
void Predfs(int u){
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;if(v==fa[u])continue;
fa[v]=u;Predfs(v);son[u].push_back(mair(v,e[i].dis));
}
}
Ver tmp[maxn],st[maxn];int tot,top;
void Dfs(int u){
if(!flag)return;
S[u].clear();
if(son[u].empty())return S[u].insert(Ver{0,0}),void();
assert(son[u].size()==2);
int x=son[u][0].fir,y=son[u][1].fir;
Dfs(x),Dfs(y);tot=0;
if(!flag)return;
{
auto itx = S[x].begin(),ity=S[y].begin(),Ed=prev(S[y].end());
while(itx != S[x].end()){
while(ity!=Ed && (*itx).b+son[u][0].sec+son[u][1].sec+(*next(ity)).a<=Lim)++ity;
if((*itx).b+son[u][0].sec+(*ity).a+son[u][1].sec<=Lim)tmp[++tot]=Ver{(*itx).a+son[u][0].sec,(*ity).b+son[u][1].sec};
++itx;
}
}
{
auto itx = S[x].begin(),ity=S[y].begin(),Ed=prev(S[x].end());
while(ity != S[y].end()){
while(itx!=Ed && (*next(itx)).a+son[u][0].sec+son[u][1].sec+(*ity).b<=Lim)++itx;
if((*ity).b+son[u][1].sec+(*itx).a+son[u][0].sec<=Lim)tmp[++tot]=Ver{(*ity).a+son[u][1].sec,(*itx).b+son[u][0].sec};
++ity;
}
}
if(!tot)return flag=false,void();
sort(tmp+1,tmp+tot+1);top=0;
st[++top]=tmp[1];
Rep(i,2,tot){
if(tmp[i].a==st[top].a)st[top].b=min(st[top].b,tmp[i].b);
else if(tmp[i].b<st[top].b)st[++top]=tmp[i];
}
Rep(i,1,top)S[u].insert(st[i]);
if(S[u].empty())flag=false;
}
}G;
void solve(){
cin>>n;int x,y,w;
Rep(i,2,n)cin>>x>>w,G.lqx(i,x,w),G.lqx(x,i,w);
G.Predfs(1);
ll l=-1,r=100000000000;
while(r-l>1){
ll mid=(l+r)>>1;
Lim=mid,flag=true;G.Dfs(1);
if(flag)r=mid;
else l=mid;
}
cout<<r<<"\n";
}
#undef int
int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }
T4 gtm和joke的星球
是斯坦纳树板子题,可能比较不友好,区分度很差,毕竟会就切了,不会也没法,当然最痛苦的是知道这是个板子但是不会打的。
如果你不知道这是个板子,那么你是更有可能拿高分甚至切掉的。在较弱的数据下,随机化做法是跑的非常优秀的(甚至在验题时我的随机化做法切掉了),应该会有不少人写随机化的。比较naive的想法就是建最小生成树然后把不用的边减掉,在树的部分分里这是绝对正确的,但是别的就不行了,于是我们考虑随机建出生成树,然后删无用边,在本题数据下可以拿到50分。数据不是我造的,如果我造的话可能会给随机化更多分,以提高一点区分度。
随机化
#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i)
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;
const int maxn=1e2+10,maxm=5e2+10;
int n,m,K;
mt19937 rd((unsigned long long ) &maxn);
int rr(int l, int r){
uniform_int_distribution <> d(l, r);
return d(rd);
}
struct UN{
int f[maxn];
void Init(int N){Rep(i,1,N)f[i]=i;}
int Find(int x){return f[x]==x ? x : f[x]=Find(f[x]);}
void Merge(int x,int y){
x=Find(x),y=Find(y);
f[y]=x;
}
}S;
struct edg{
int from,to,dis;
bool operator<(const edg &rhs)const{ return dis<rhs.dis; }
}es[maxm];
int a[maxn];
int ans=1000000000;
struct Graph{
struct eg{int from,to,dis,next;}e[maxm];
int len,head[maxn];int dp[maxn],siz[maxn];
void Clear(){
Rep(i,1,n)dp[i]=siz[i]=head[i]=0;len=0;
Rep(i,1,K)siz[a[i]]=1;
}
void lqx(int from,int to,int dis)
{ e[++len].from=from,e[len].to=to,e[len].next=head[from],e[len].dis=dis,head[from]=len;}
void Dfs(int u,int fa){
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;if(v==fa)continue;
Dfs(v,u);
if(siz[v])dp[u]+=dp[v]+e[i].dis;
siz[u]+=siz[v];
}
}
}G;
bool Check(){
set<int>tmp;
Rep(i,1,K)tmp.insert(S.Find(a[i]));
return (tmp.size()==1);
}
void solve(){
cin>>n>>m>>K;S.Init(n);
if(K==0)return cout<<"0\n",void();
Rep(i,1,m)cin>>es[i].from>>es[i].to>>es[i].dis;
Rep(i,1,K)cin>>a[i];
sort(es+1,es+m+1);
Rep(i,1,K)G.siz[a[i]]=1;
Rep(i,1,m){
if(S.Find(es[i].from)==S.Find(es[i].to))continue;
S.Merge(es[i].from,es[i].to);
G.lqx(es[i].from,es[i].to,es[i].dis);
G.lqx(es[i].to,es[i].from,es[i].dis);
}
G.Dfs(a[1],0);
ans=min(ans,G.dp[a[1]]);
do{
S.Init(n);G.Clear();
shuffle(es+1,es+m+1,rd);
Rep(i,1,m){
if(S.Find(es[i].from)==S.Find(es[i].to))continue;
S.Merge(es[i].from,es[i].to);
G.lqx(es[i].from,es[i].to,es[i].dis);
G.lqx(es[i].to,es[i].from,es[i].dis);
}
G.Dfs(a[1],0);
ans=min(ans,G.dp[a[1]]);
if(1.0*clock()/CLOCKS_PER_SEC>=0.950)break;
}while(1);
cout<<ans<<"\n";
}
int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }
正解
#include <bits/stdc++.h>
typedef long long ll;typedef unsigned long long ull; typedef double db;typedef long double ldb;
#define fre(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define Rep(i,a,b) for(int i=a;i<=b;++i)
#define Dwn(i,a,b) for(int i=a;i>=b;--i)
#define pii pair<int,int>
#define mair make_pair
#define fir first
#define sec second
using namespace std;
const int maxn=1e2+10,maxm=1e3+10,INF=0x3f3f3f3f;
int dp[maxn][1<<11];
int n,m,K;
int a[maxn];
priority_queue<pii>q;
struct Graph{
struct eg{int from,to,next,dis;}e[maxm];
int len,head[maxn];bool vis[maxn];
void lqx(int from,int to,int dis)
{ e[++len].from=from,e[len].to=to,e[len].next=head[from],e[len].dis=dis,head[from]=len; }
void Dij(int s){
memset(vis,0,sizeof(vis));
while(!q.empty()){
int u=q.top().sec;q.pop();
if(vis[u])continue;
vis[u]=true;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(dp[v][s]>dp[u][s]+e[i].dis){
dp[v][s]=dp[u][s]+e[i].dis;
q.push(mair(-dp[v][s],v));
}
}
}
}
}G;
void solve(){
fre(steiner);
cin>>n>>m>>K;int x,y,w;
memset(dp,0x3f,sizeof(dp));
Rep(i,1,m)cin>>x>>y>>w,G.lqx(x,y,w),G.lqx(y,x,w);
Rep(i,1,K){
cin>>a[i];
dp[a[i]][1<<(i-1)]=0;
}
for(int s=1;s<(1<<K);++s){
Rep(i,1,n){
for(int sub=s&(s-1);sub;sub=s&(sub-1))dp[i][s]=min(dp[i][s],dp[i][sub]+dp[i][s^sub]);
if(dp[i][s]!=INF){q.push(mair(-dp[i][s],i));}
}
G.Dij(s);
}
cout<<dp[a[1]][(1<<K)-1];
}
int main (){ ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);return solve(),0; }
标签:typedef,51nod,long,int,maxn,模拟,CSP,es,define 来源: https://www.cnblogs.com/Delov/p/16674006.html