[CSP-S模拟测试92]题解
A.数列
显然每个数的答案是互相独立的,直接扩欧求解。我们需要最小化$ax+by=gcd(a,b)$中的$|x|+|y|$,而显然当x或y靠近0时答案可能最优,列个不等式求一下即可。
能$O(1)$千万不要懒。
#include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> using namespace std; const int N=1e5+5; int read() { int 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 x*f; } typedef long long ll; int n; ll s[N]; ll x,y,A,B; ll gcd(ll a,ll b) { if(!b)return a; return gcd(b,a%b); } ll abss(ll x) { return x>0?x:-x; } void exgcd(ll a,ll b,ll &x,ll &y) { if(b==0) { x=1;y=0;return ; } exgcd(b,a%b,x,y); ll t=x;x=y;y=t-a/b*y; } void test() { A=read();B=read();int G=gcd(A,B); cout<<G<<endl; exgcd(A,B,x,y); cout<<x<<' '<<y<<endl; } int main() { //freopen("array.in","r",stdin); n=read();A=read();B=read(); for(int i=1;i<=n;i++) s[i]=read(); ll ans=0;ll G=gcd(A,B),ta=A/G,tb=B/G; exgcd(A/G,B/G,x,y); for(int i=1;i<=n;i++) { if(s[i]%G!=0) { puts("-1"); exit(0); } ll res=1e15; ll t=s[i]/G; ll nowx=x*t,nowy=y*t; res=min(res,abss(nowx)+abss(nowy)); ll p=-nowx/tb; res=min(res,abss(nowx+p*tb)+abss(nowy-p*ta)); res=min(res,abss(nowx+(p-1)*tb)+abss(nowy-(p-1)*ta)); res=min(res,abss(nowx+(p+1)*tb)+abss(nowy-(p+1)*ta)); p=-nowy/ta; res=min(res,abss(nowx-p*tb)+abss(nowy+p*ta)); res=min(res,abss(nowx-(p-1)*tb)+abss(nowy+(p-1)*ta)); res=min(res,abss(nowx-(p+1)*tb)+abss(nowy+(p+1)*ta)); //cout<<nowx<<' '<<nowy<<endl; ans+=res; } printf("%lld\n",ans); return 0; }
B.数对
可任意排序看似难以解决,但考虑一下$a$和$b$之间的限制,不难发现如果$a_i<b_j \ \ and\ \ b_i<a_j$,$i$应当尽可能在$j$前面,如果恰好相反那么$i$就应该在后面,至于剩下的情况,怎么排都是一样的。那么按$a+b$排序就能保证选取一定最优。
之后就是原题了。线段树维护dp板子。
(忽然发现我好像没写过之前那题的题解 懒癌发作失败)
那么现在问题就转化成了给你一个二元组序列,要求你按顺序选几个二元组,在满足所有$a_i \leq b_j \ (i<j)$的情况下最大化权值。
设$dp[i][j]$表示当前考虑到i并且选了它,且所选集合中最大的$a[]$值为j的最大收益。($a[]$和$b[]$当然是要离散化的= =)
暴力$O(n^3)$转移就很显然了:
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<vector> using namespace std; int read() { int 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 x*f; } const int N=1e5+5; typedef long long ll; int n,a[N],b[N],S; ll w[N],dp[2505][2505],ans; vector<int> c; struct node { int a,b;ll w; }s[N]; bool cmp(node x,node y) { if(max(x.b,x.a)!=max(y.b,y.a)) return max(x.b,x.a)<max(y.b,y.a); else return x.b<y.a; } int main() { //freopen("pair.in","r",stdin); n=read(); for(int i=1;i<=n;i++) s[i].a=read(),s[i].b=read(),s[i].w=read(); sort(s+1,s+n+1,cmp); for(int i=1;i<=n;i++) a[i]=s[i].a,b[i]=s[i].b,c.push_back(a[i]),c.push_back(b[i]),w[i]=s[i].w; sort(c.begin(),c.end());vector<int>::iterator it=unique(c.begin(),c.end()); for(int i=1;i<=n;i++) { a[i]=lower_bound(c.begin(),it,a[i])-c.begin()+1; b[i]=lower_bound(c.begin(),it,b[i])-c.begin()+1; S=max(S,max(a[i],b[i])); } for(int i=1;i<=n;i++) { dp[i][a[i]]=w[i]; for(int j=1;j<i;j++) for(int k=1;k<=b[i];k++) dp[i][max(k,a[i])]=max(dp[i][max(k,a[i])],dp[j][k]+w[i]),ans=max(ans,dp[i][max(a[i],k)]); } cout<<ans<<endl; return 0; }
然后你发现可以直接把第二维扔到线段树上维护,区间修改区间查询单点更新即可。
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<vector> using namespace std; int read() { int 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 x*f; } const int N=1e5+5; typedef long long ll; int n,a[N],b[N],S; ll w[N],dp[N],ans; vector<int> c; struct node { int a,b;ll w; }s[N]; bool cmp(node x,node y) { return (x.a+x.b)<(y.a+y.b); } ll maxv[N<<3],lz[N<<3]; #define ls(k) (k)<<1 #define rs(k) (k)<<1|1 #define up maxv[k]=max(maxv[ls(k)],maxv[rs(k)]) void down(int k) { if(!lz[k])return ; lz[ls(k)]+=lz[k]; lz[rs(k)]+=lz[k]; maxv[ls(k)]+=lz[k]; maxv[rs(k)]+=lz[k]; lz[k]=0; } void update(int k,int l,int r,int pos,ll val) { if(l==r) { maxv[k]=max(maxv[k],val); return ; } down(k); int mid=l+r>>1; if(pos<=mid)update(ls(k),l,mid,pos,val); else update(rs(k),mid+1,r,pos,val); up; } ll ask(int k,int l,int r,int L,int R) { if(l>r)return 0; if(L<=l&&R>=r)return maxv[k]; down(k); int mid=l+r>>1;ll res=0; if(L<=mid)res=max(res,ask(ls(k),l,mid,L,R)); if(R>mid)res=max(res,ask(rs(k),mid+1,r,L,R)); return res; } void change(int k,int l,int r,int L,int R,ll val) { if(l>r)return ; if(L<=l&&R>=r) { maxv[k]+=val; lz[k]+=val; return ; } down(k); int mid=l+r>>1; if(L<=mid)change(ls(k),l,mid,L,R,val); if(R>mid)change(rs(k),mid+1,r,L,R,val); up; } int main() { //freopen("pair.in","r",stdin); n=read(); for(int i=1;i<=n;i++) s[i].a=read(),s[i].b=read(),s[i].w=read(); sort(s+1,s+n+1,cmp); for(int i=1;i<=n;i++) a[i]=s[i].a,b[i]=s[i].b,c.push_back(a[i]),c.push_back(b[i]),w[i]=s[i].w; sort(c.begin(),c.end());vector<int>::iterator it=unique(c.begin(),c.end()); for(int i=1;i<=n;i++) { a[i]=lower_bound(c.begin(),it,a[i])-c.begin()+1; b[i]=lower_bound(c.begin(),it,b[i])-c.begin()+1; S=max(S,max(a[i],b[i])); } for(int i=1;i<=n;i++) { if(a[i]>b[i]) dp[i]=max(dp[i],ask(1,1,S,1,b[i])+w[i]); else { dp[i]=max(dp[i],ask(1,1,S,1,a[i])+w[i]); change(1,1,S,a[i],b[i],w[i]); } update(1,1,S,a[i],dp[i]); } cout<<ask(1,1,S,1,S)<<endl; return 0; }
C.最小距离
什么?dj可以跑多源最短路?
一开始把所有特殊点都扔到堆里,转移的时候记录该点由哪个特殊点转移过来,最后枚举边把答案拼一下就好了。
因为每个点只会被更新一次,所以跑出来每个点的$dis[]$必然为离它最近的那个特殊点的距离。枚举边的时候,如果两端不是同一个点转移而来,就可以用两个$dis[]$+边权更新答案。
#include<bits/stdc++.h> using namespace std; int read() { int 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 x*f; } #define pa pair<ll,int> const int N=2e5+5; typedef long long ll; int n,m,p; bool isp[N]; int to[N<<1],head[N],nxt[N<<1],vis[N],sp[N],tot,fr[N]; ll w[N<<1],ans[N<<1],dis[N]; void add(int x,int y,ll z) { to[++tot]=y; nxt[tot]=head[x]; head[x]=tot; w[tot]=z; } void dj() { priority_queue<pa> q; for(int i=1;i<=n;i++) dis[i]=1e15,vis[i]=0; for(int i=1;i<=p;i++) q.push(make_pair(0,sp[i])),fr[sp[i]]=sp[i],dis[sp[i]]=0; while(!q.empty()) { int x=q.top().second;q.pop(); if(vis[x])continue; vis[x]=1; for(int i=head[x];i;i=nxt[i]) { int y=to[i]; if(dis[y]>dis[x]+w[i]) dis[y]=dis[x]+w[i],fr[y]=fr[x],q.push(make_pair(-dis[y],y)); } } for(int i=1;i<=m;i++) { int x=to[i*2-1],y=to[i*2]; if(fr[x]!=fr[y]) { ans[fr[x]]=min(ans[fr[x]],dis[x]+w[i*2]+dis[y]); ans[fr[y]]=min(ans[fr[y]],dis[x]+w[i*2]+dis[y]); } } } int main() { n=read();m=read();p=read(); for(int i=1;i<=p;i++) { int x=read(); sp[i]=x; isp[x]=1; } for(int i=1;i<=n;i++) ans[i]=1e15; for(int i=1;i<=m;i++) { int x=read(),y=read();ll z=read(); add(x,y,z);add(y,x,z); } dj(); for(int i=1;i<=p;i++) printf("%lld ",ans[sp[i]]); return 0; }
兴许青竹早凋,碧梧已僵,人事本难防。