JOISC2018 题解
A. Construction of Highway
题目大意
给
个点,初始每个点有权值 , 次操作连一条边 ,其中 与 连通, 与 不连通,求 路径上权值的逆序对数,然后把路径权值推平为 的权值。 数据范围:
。
思路分析
考虑 LCT,注意到每次连边后
因此在
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+1,W=1e9;
int n,w[MAXN]; ll ans;
struct FenwickTree {
int tr[MAXN]; ll s;
vector <int> p;
inline void add(int x,int v) { for(p.push_back(x);x<=n;x+=x&-x) tr[x]+=v; }
inline ll qry(int x) { for(s=0;x;x&=x-1) s+=tr[x]; return s; }
inline void clr() { for(int x:p) for(;x<=n;x+=x&-x) tr[x]=0; p.clear(); }
} T;
namespace LCT {
int fa[MAXN],ch[MAXN][2],col[MAXN],cov[MAXN],siz[MAXN];
inline void psu(int p) { siz[p]=siz[ch[p][0]]+siz[ch[p][1]]+1; }
inline void adt(int x,int c) { if(x) cov[x]=col[x]=c; }
inline void psd(int x) { if(cov[x]) adt(ch[x][0],cov[x]),adt(ch[x][1],cov[x]),cov[x]=0; }
inline bool nrt(int p) { return ch[fa[p]][0]==p||ch[fa[p]][1]==p; }
inline int son(int x) { return ch[fa[x]][1]==x; }
inline void upd(int p) { if(nrt(p)) upd(fa[p]); psd(p); }
inline void rot(int p) {
int u=fa[p],v=fa[u],k=son(p);
if(nrt(u)) ch[v][son(u)]=p;
fa[ch[u][k]=ch[p][k^1]]=u,fa[fa[ch[p][k^1]=u]=p]=v;
psu(p),psu(u);
}
inline void splay(int p) {
for(upd(p);nrt(p);rot(p)) if(nrt(fa[p])) rot(son(p)==son(fa[p])?fa[p]:p);
}
inline int access(int p) {
int u=0;
for(ans=0,T.clr();p;u=p,p=fa[p]) {
splay(p);
int tmp=1+siz[ch[p][0]];
ans+=1ll*tmp*T.qry(col[p]-1);
T.add(col[p],tmp);
ch[p][1]=u,psu(p);
}
return u;
}
}
using namespace LCT;
signed main() {
scanf("%d",&n);
vector <int> vals;
for(int i=1;i<=n;++i) scanf("%d",&w[i]),vals.push_back(w[i]);
sort(vals.begin(),vals.end()),vals.erase(unique(vals.begin(),vals.end()),vals.end());
for(int i=1;i<=n;++i) w[i]=lower_bound(vals.begin(),vals.end(),w[i])-vals.begin()+1;
col[1]=w[1],siz[1]=1;
for(int i=1;i<n;++i) {
int u,v;
scanf("%d%d",&u,&v);
adt(access(u),w[v]);
printf("%lld\n",ans);
fa[v]=u,col[v]=w[v],siz[v]=1;
}
return 0;
}
C. Fences
题目大意
的网格上选若干个(至少一个)格子染色并指定上下左右,满足任意两个同行或同列的格子的朝向必须相对,求方案数。 数据范围:
。
思路分析
设
- 第
行无黑格:从 转移。 - 第
行有一个黑格,设在第 列:- 若第
列只有一个黑格:那么第 行的黑格有 种位置和 种朝向,从 转移。 - 若第
列有两个黑格:显然两个黑格相对,那么选定两个黑格的位置后不能选朝向,且同行不能选其他黑格,从 转移。
- 若第
- 若第
行有两个黑格:显然对应两列不能放其他黑格,从 转移。
因此得到状态转移方程:
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=3001,MOD=1e9+7;
int dp[MAXN][MAXN];
signed main() {
int n,m;
scanf("%lld%lld",&n,&m);
for(int i=0;i<=max(n,m);++i) dp[0][i]=dp[i][0]=1;
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) {
dp[i][j]=(dp[i-1][j]+4*j*dp[i-1][j-1])%MOD;
if(i>1) dp[i][j]=(dp[i][j]+(i-1)*j%MOD*dp[i-2][j-1]%MOD)%MOD;
if(j>1) dp[i][j]=(dp[i][j]+j*(j-1)/2%MOD*dp[i-1][j-2]%MOD)%MOD;
}
printf("%lld\n",(dp[n][m]+MOD-1)%MOD);
return 0;
}
D. Asceticism
题目大意
给定
,求有多少长度为 的排列 满足 。 数据范围:
。
思路分析
对于这类求升高的问题,经典的想法就是容斥,钦定
此时把连续的
注意到这实际上等价于将
二项式反演得到:
其中最后一步我们用到了如下恒等式:
组合意义解释为:
注意我们要求的答案是
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5,MOD=1e9+7;
ll fac[MAXN],ifac[MAXN];
inline ll ksm(ll a,ll b=MOD-2,ll p=MOD) {
ll ret=1;
for(;b;a=a*a%p,b=b>>1) if(b&1) ret=ret*a%p;
return ret;
}
inline ll binom(int n,int m) { return fac[n]*ifac[m]%MOD*ifac[n-m]%MOD; }
signed main() {
int n,m;
scanf("%d%d",&n,&m),--m;
for(int i=fac[0]=ifac[0]=1;i<=n+1;++i) ifac[i]=ksm(fac[i]=fac[i-1]*i%MOD);
ll ans=0;
for(int i=1;i<=n-m;++i) ans=(ans+MOD+((n-m-i)%2?-1:1)*ksm(i,n)*binom(n+1,m+i+1)%MOD)%MOD;
printf("%lld\n",ans);
return 0;
}
E. Road Service
题目大意
本题为提交答案题。
给定一棵
个点的树,在上面增加 条无向边,最小化 ,其中 表示 的最短路长度。 数据范围:
。
思路分析
容易发现比较优的解一定是把
考虑模拟退火,每次更改一条边的端点,但
对于初始态,我们可以对枚举起点
但这样退火的初始态也不够优秀,考虑按度数从大到小将点添加进
挂一会就跑出来了。
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1005,inf=1e9;
mt19937 rnd(time(0));
int n,k,w0;
inline double rd() {
return uniform_real_distribution<>(0,1)(rnd);
}
bool vis[MAXN];
int d[MAXN],bs,ans=inf,que[MAXN];
vector <int> G[MAXN],E,R,S[MAXN][5];
inline int qval(int ht) {
memset(d,0x3f,sizeof(d));
int hd=1,tl=1;
que[1]=ht,d[ht]=0;
for(int i:E) que[++tl]=i,d[i]=1;
while(hd<=tl) {
int u=que[hd++];
for(int v:G[u]) if(d[v]>d[u]+1) {
d[v]=d[u]+1,que[++tl]=v;
}
}
return accumulate(d+1,d+n+1,0);
}
inline int qvalALL(int ht,int ti) {
int sum=0;
for(int i=1;i<=n;++i) {
memset(d,0x3f,sizeof(d));
int hd=1,tl=1;
que[1]=i,d[i]=0;
while(hd<=tl) {
int u=que[hd++];
for(int v:G[u]) if(d[v]>d[u]+1) {
d[v]=d[u]+1,que[++tl]=v;
}
if(u==ht) {
for(int v:S[ht][ti]) if(d[v]>d[u]+1) {
d[v]=d[u]+1,que[++tl]=v;
}
}
if(vis[u]) {
if(d[ht]>d[u]+1) {
d[ht]=d[u]+1,que[++tl]=ht;
}
}
}
sum+=accumulate(d+1,d+n+1,0);
}
return sum;
}
int rk[MAXN];
double wt[MAXN];
signed main() {
scanf("%d%d%d",&n,&k,&w0);
for(int i=0,u,v;i<n;++i) scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
vector <array<int,3>> vals;
for(int ht=1;ht<=n;++ht) for(int ti:{0,1,2,3,4}) {
memset(vis,false,sizeof(vis));
for(int i=1;i<=n;++i) if(i!=ht) wt[i]=G[i].size()+rd()*10;
wt[ht]=-inf;
for(int i=1;i<=n;++i) rk[i]=i;
sort(rk+1,rk+n+1,[&](int x,int y){ return wt[x]<wt[y]; });
for(int i=1;i<=k;++i) S[ht][ti].push_back(rk[i]),vis[rk[i]]=true;
vals.push_back({qvalALL(ht,ti),ht,ti});
}
sort(vals.begin(),vals.end());
for(int t=0;t<10;++t) {
int ht=vals[t][1],ti=vals[t][2];
memset(vis,false,sizeof(vis));
E=S[ht][ti];
int tmp=qval(ht);
auto get=[&]() {
int x;
do x=rnd()%n+1;
while(x==ht||vis[x]);
return x;
};
if(tmp<ans) ans=tmp,R=E,bs=ht;
for(double T=10000;T>1e-10;T*=0.9999) {
int i=get(),id=rnd()%k,j=E[id];
vis[j]=false,vis[i]=true,E[id]=i;
tmp=qval(ht);
if(tmp<ans) ans=tmp,bs=ht,R=E;
else if(rnd()<=exp((ans-tmp)/T));
else vis[j]=true,vis[i]=false,E[id]=j;
}
}
for(int i:R) printf("%d %d\n",bs,i);
}
F. Worst Reporter 3
题目大意
数轴上有
个人,编号 ,第 个人初始在 上。 每轮第
个人向右移动 ,然后对于 ,如果其与 距离 ,那么就移动到第 左边的位置。
次询问 秒后 内有几个人。 数据范围:
。
思路分析
根据数学归纳法可以证明每个人的运动都是具有周期性的,即每隔
然后二分
事实上,注意到
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=5e5+1;
int d[MAXN],k[MAXN];
signed main() {
int n,q;
scanf("%lld%lld",&n,&q);
for(int i=1;i<=n;++i) scanf("%lld",&d[i]);
d[0]=k[0]=1;
for(int i=1;i<=n;++i) k[i]=(d[i]+k[i-1]-1)/k[i-1]*k[i-1];
auto query=[&](int t,int x) {
int l=0,r=n,res=-1;
while(l<=r) {
int mid=(l+r)>>1;
if((t/k[mid])*k[mid]-mid>=x) res=mid,l=mid+1;
else r=mid-1;
}
return res+1;
};
while(q--) {
int t,l,r;
scanf("%lld%lld%lld",&t,&l,&r);
printf("%lld\n",query(t,l)-query(t,r+1));
}
return 0;
}
G. Airline Route Map
题目大意
通信题。
A.cpp
输入一张个点的图 ,输出一个点数不超过 的图 。 - 交互器任意打乱
中点和边的标号得到 。 B.cpp
输入,还原 。 数据范围:
。
思路分析
思路肯定是要用多的
用其中的
那么原问题就变成了如何找到 B.cpp
的角度思考,观察我们能快速在
在所有点都不确定的情况下,最容易想到的就是有关度数的信息,可以考虑让
那么找度数最大的点
考虑用
谈到确定顺序,显然的想法是把
代码呈现
A.cpp
:
#include <bits/stdc++.h>
#include "Alicelib.h"
using namespace std;
void Alice(int N,int M,int A[],int B[]){
vector <array<int,2>> Edges;
for(int i=0;i<M;++i) Edges.push_back({A[i],B[i]});
for(int i=0;i<N;++i) {
for(int k=0;k<10;++k) {
if((i>>k)&1) Edges.push_back({i,N+k});
}
}
for(int i=0;i<10;++i) Edges.push_back({N+i,N+10});
for(int i=1;i<10;++i) Edges.push_back({N+i-1,N+i});
for(int i=0;i<N+10;++i) Edges.push_back({N+11,i});
InitG(N+12,Edges.size());
for(int i=0;i<(int)Edges.size();++i) MakeG(i,Edges[i][0],Edges[i][1]);
}
B.cpp
:
#include<bits/stdc++.h>
#include "Boblib.h"
using namespace std;
void Bob(int V,int U,int C[],int D[]){
vector <int> degs(V);
vector <vector<int>> adj(V);
for(int i=0;i<U;++i) {
adj[C[i]].push_back(D[i]);
adj[D[i]].push_back(C[i]);
++degs[C[i]],++degs[D[i]];
}
int st=0;
for(int i=0;i<V;++i) if(degs[i]>degs[st]) st=i;
assert(degs[st]==V-2);
vector <int> typ(V,2);//graph_node[0] bit_seq[1] bit_marker[2] start_node[3]
typ[st]=3;
for(int u:adj[st]) typ[u]=0;
int bm=-1; //bit_marker
for(int i=0;i<V;++i) if(typ[i]==2) { bm=i; break; }
assert(bm!=-1);
for(int u:adj[bm]) typ[u]=1;
vector <array<int,2>> b_adj(V,{V,V}); //adj in bit_seq
int b_top=-1; //top of the bit_seq
for(int u:adj[bm]) {
for(int v:adj[u]) {
if(typ[v]==1) {
if(b_adj[u][0]<V) b_adj[u][1]=v;
else b_adj[u][0]=v;
}
}
if(b_adj[u][1]==V) b_top=u;
}
assert(b_top!=-1);
vector <int> bs{b_top}; //bit_seq
for(int p=b_top,fa=V;;) {
int tmp=p;
p=b_adj[p][0]^b_adj[p][1]^fa,fa=tmp;
bs.push_back(p);
if(b_adj[p][1]==V) break;
}
assert(bs.size()==10);
if(degs[bs.front()]<degs[bs.back()]) reverse(bs.begin(),bs.end());
vector <int> idx(V);
for(int k=0;k<10;++k) {
for(int u:adj[bs[k]]) if(!typ[u]) idx[u]|=1<<k;
}
vector <array<int,2>> Edges;
for(int i=0;i<U;++i) if(!typ[C[i]]&&!typ[D[i]]) Edges.push_back({idx[C[i]],idx[D[i]]});
InitMap(V-12,Edges.size());
for(auto e:Edges) MakeMap(e[0],e[1]);
}
H. Bitaro's Party
题目大意
给定一张
个点 条边的有向图, 次询问 并给定 个点,求 中除这 个点之外的点到 的最长距离。 数据范围:
,每条边都是从编号小的点连向到编号大的点。
思路分析
考虑根号分治,若
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e5+5,inf=2e9;
vector <array<int,2>> P[MAXN];
vector <int> G[MAXN];
int dis[MAXN],mark[MAXN],mp[MAXN];
signed main() {
int n,m,q;
scanf("%d%d%d",&n,&m,&q);
int B=sqrt(n);
for(int i=1;i<=m;++i) {
int u,v;
scanf("%d%d",&u,&v);
G[v].push_back(u);
}
fill(mp+1,mp+n+1,-inf);
for(int u=1;u<=n;++u) {
P[u].push_back({0,u});
for(int v:G[u]) for(auto I:P[v]) mp[I[1]]=max(mp[I[1]],I[0]+1);
for(int v:G[u]) {
int ed=P[u].size();
for(auto I:P[v]) if(mp[I[1]]==I[0]+1) {
P[u].push_back({mp[I[1]],I[1]}),mp[I[1]]=-inf;
}
inplace_merge(P[u].begin(),P[u].begin()+ed,P[u].end(),greater<array<int,2>>());
if((int)P[u].size()>B) P[u].resize(B);
}
}
for(int T=1;T<=q;++T) {
int t,y;
scanf("%d%d",&t,&y);
for(int i=0,j;i<y;++i) scanf("%d",&j),mark[j]=T;
for(auto p:P[t]) {
if(mark[p[1]]<T) {
printf("%d\n",p[0]);
goto ok;
}
}
if((int)P[t].size()<B) puts("-1");
else {
fill(dis+1,dis+t+1,-inf),dis[t]=0;
for(int u=t;u;--u) {
for(int v:G[u]) dis[v]=max(dis[v],dis[u]+1);
}
int ans=-1;
for(int i=1;i<=t;++i) if(mark[i]<T) ans=max(ans,dis[i]);
printf("%d\n",ans<0?-1:ans);
}
ok:;
}
return 0;
}
I. Security Gate
题目大意
定义一个括号序列是“好的”当且仅当能够将一个该括号序列的一个可空子串的左右括号反转从而得到一个合法括号序列。
给定一个长度为
有空白位置的括号串,求有多少种在空白中填左括号或右括号的方式,使得最终的括号串是“好的”。 数据范围:
。
思路分析
令左括号为
定义括号序列翻转表示整个序列变成
设第
分类讨论两侧的合法情况。
-
两侧前缀和均合法。
那么显然整个括号序列合法,直接简单 dp 即可。
-
恰有一侧前缀和不合法。
不妨假设所有
但存在 。找到第一个
的位置 ,一定有 。注意到从
越大的位置翻转越优,因此直接找 中的最大值作为 。注意到
的点翻转后的前缀和会变成 ,这些位置一定合法。设
,那么我们要找一个 ,显然 越小越好。如果
,那么可以在 中找到一个合法的 。否则我们要 dp,满足
中不能有超过 的数。考虑从后往前维护
的值 ,我们要找的 就是 ,限制就是 满足 。枚举
然后 dp,前缀只要记录当前和和历史最大前缀和简单预处理,设 表示 且 的方案数。后缀 dp 设
表示 , 表示没确定 , 表示确定了 。最终答案为
。 -
两侧前缀均不合法。
找到第一个
的位置 和最后一个 的位置 。设
的最大值为 , ,那么我们要在 范围内找一个 。不妨设
,那么要求 ,若 ,则将整个序列翻转后得到 。依然是将整个序列减掉
,设 ,我们依然要找 。前缀 dp 依然不变,后缀 dp 设
表示 , 表示没确定 , 表示确定了 , 表示确定了 。若
,那么要求 ,和上一部分一样简单 dp,注意 在 时可以 。最终答案为
。但这样会算重贡献,即
的情况会被算两次,那么单独求一下这种情况的方案数。上面的 dp 一样,但在
时需要满足 ,其他不变。
分别用 dp 处理即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=305,MOD=1e9+7;
int n;
char str[MAXN];
inline void add(int &x,int y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
inline int DP0() { //both side valid
static int dp[MAXN][MAXN];
//dp[i,j]: S[i]=j
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int i=0;i<n;++i) for(int j=0;j<=i;++j) if(dp[i][j]) {
if(str[i+1]!=')') add(dp[i+1][j+1],dp[i][j]);
if(str[i+1]!='('&&j>0) add(dp[i+1][j-1],dp[i][j]);
}
return dp[n][0];
}
int f[MAXN][MAXN];
//f[i,j]: S[1~i]=-1, max prefix=j
inline void DP1() {
static int dp[2][MAXN][MAXN];
//dp[i,j,k]: S[i]=j, max prefix=k
memset(f,0,sizeof(f));
memset(dp,0,sizeof(dp));
dp[0][0][0]=1;
for(int i=0;i<n;++i) {
int cur=i&1; memset(dp[cur^1],0,sizeof(dp[cur^1]));
for(int j=0;j<=i;++j) for(int k=j;k<=i;++k) if(dp[cur][j][k]) {
if(str[i+1]!=')') add(dp[cur^1][j+1][max(k,j+1)],dp[cur][j][k]);
if(str[i+1]!='(') {
if(j>0) add(dp[cur^1][j-1][k],dp[cur][j][k]);
else add(f[i+1][k],dp[cur][j][k]);
}
}
}
}
inline int DP2() { //prefix invalid, suffix valid
static int dp[MAXN][MAXN][2];
//dp[i,j,k]: revS[i]=j, k={0: not fix r,1: fix r}
int ans=0;
for(int s=0;s<=n;++s) { //s=a-b/2
memset(dp,0,sizeof(dp));
dp[n][0][0]=1;
for(int i=n;i>=1;--i) for(int j=0;j<=n-i;++j) for(int k:{0,1}) {
if(k&&j>s*2) dp[i][j][k]=0; //exceed 2a
if(k&&j==s) dp[i][j][k]=dp[i][j][0]; //find a+b/2
if(dp[i][j][k]) {
if(str[i]!='(') add(dp[i-1][j+1][k],dp[i][j][k]);
if(str[i]!=')'&&j>0) add(dp[i-1][j-1][k],dp[i][j][k]);
}
}
for(int i=1;i<=n;++i) { //pos of first -1
for(int a=0;a<=n;++a) if(f[i][a]) {
int b=(a-s)*2; //S[1~n]
if(-n<=b&&b<0) {
add(ans,1ll*f[i][a]*dp[i][-b-1][a+b/2<0]%MOD);
//if a+b/2>=0, we can get a+b/2 in S[1]~S[i]
}
}
}
}
return ans;
}
inline int DP3(bool flg) { //both side invalid
//flg={0:c >= a+b/2,1:c = a+b/2}
static int dp[MAXN][MAXN<<1][3];
//dp[i,j,k]: revS[i]=j-n, k={0: not fix r, 1: fix r, 2: fix last -1}
int ans=0;
for(int s=0;s<=n;++s) { //s=a-b/2
memset(dp,0,sizeof(dp));
dp[n][n][0]=1;
for(int i=n;i>=1;--i) for(int j=i-n;j<=n-i;++j) for(int k:{0,1,2}) {
if(k!=0&&j>s*2) dp[i][j+n][k]=0; //exceed 2a
if(k==1&&j==s) dp[i][j+n][k]=dp[i][j+n][0]; //find a+b/2
if(flg&&k!=2&&j>s) dp[i][j+n][k]=0; //flg: c can not exceed a+b/2
if(dp[i][j+n][k]) {
if(str[i]!='(') add(dp[i-1][j+1+n][k],dp[i][j+n][k]);
if(str[i]!=')') {
if(j>0) add(dp[i-1][j-1+n][k],dp[i][j+n][k]);
else if(k!=0) add(dp[i-1][j-1+n][2],dp[i][j+n][k]);
//-1 can only appear in part of k=2
}
}
}
for(int i=1;i<=n;++i) { //pos of first -1
for(int a=0;a<=n;++a) if(f[i][a]) {
int b=(a-s)*2;
if(-n<=b&&b<=n) add(ans,1ll*f[i][a]*dp[i][-b-1+n][2]%MOD);
}
}
}
return ans;
}
signed main() {
scanf("%d%s",&n,str+1);
if(n&1) return puts("0"),0;
DP1();
int ans=(0ll+DP0()+DP2()+DP3(0)+MOD-DP3(1))%MOD;
reverse(str+1,str+n+1);
for(int i=1;i<=n;++i) if(str[i]!='x') str[i]^=1;
DP1();
ans=(0ll+ans+DP2()+DP3(0))%MOD;
printf("%d\n",ans);
return 0;
}
J. Candies
题目大意
给一个长度为
的数轴,每个位置有权值,对于 ,求出选 个不相邻位置的最大权值和。 数据范围:
。
思路分析
观察解的形态,用
用链表维护合并的过程,堆贪心求最大的一段操作即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=2e5+5,INF=1e18;
int pre[MAXN],suf[MAXN],inq[MAXN],c[MAXN];
signed main() {
int n;
scanf("%lld",&n);
for(int i=1;i<=n;++i) scanf("%lld",&c[i]),pre[i]=i-1,suf[i]=i+1,inq[i]=1;
c[0]=c[n+1]=-INF;
auto merge=[&](int u) {
pre[suf[u]]=pre[u];
suf[pre[u]]=suf[u];
inq[u]=0;
};
priority_queue <array<int,2>> Q;
for(int i=1;i<=n;++i) Q.push({c[i],i});
int ans=0;
for(int i=1;i<=(n+1)/2;++i) {
while(!inq[Q.top()[1]]) Q.pop();
int w=Q.top()[0],p=Q.top()[1]; Q.pop();
printf("%lld\n",ans+=w);
Q.push({c[p]=c[pre[p]]+c[suf[p]]-w,p});
merge(pre[p]),merge(suf[p]);
}
return 0;
}
K. Library
题目大意
交互器有一个
的排列 ,你每次可以询问 里的元素在 上构成了几个连续段。 在
次询问内还原出排列。 数据范围:
。
思路分析
考虑增量法,注意到询问
先用
然后动态维护已知前缀
交互次数
时间复杂度
代码呈现
#include<bits/stdc++.h>
#include "library.h"
using namespace std;
void Solve(int N) {
if(N==1) { Answer({1}); return ; }
int st=-1;
vector <int> rest;
auto Adj=[&](int x,vector <int> V) -> int {
vector <int> q(N,0);
for(int v:V) q[v]=1;
int d=Query(q);
q[x]=1;
return d-Query(q)+1;
};
for(int i=0;i<N;++i) {
vector <int> V;
for(int j=0;j<N;++j) if(i!=j) V.push_back(j);
int t=Adj(i,V);
if(t==1) { st=i,rest=V; break; }
}
vector <int> ans{st};
while((int)ans.size()<N) {
auto Ne=[&](int x,vector <int> V) -> int {
while(V.size()>1) {
int mid=V.size()>>1;
vector <int> V0(V.begin(),V.begin()+mid),V1(V.begin()+mid,V.end());
V=Adj(x,V0)?V0:V1;
}
return V[0];
};
int u=ans.back(),v=Ne(u,rest);
rest.erase(lower_bound(rest.begin(),rest.end(),v));
ans.push_back(v);
}
for(int &u:ans) ++u;
Answer(ans);
}
L. Wild Boar
题目大意
给一张
个点 条边的无向图,定义一条路径合法当且仅当不会连续经过同一条边。 动态维护一个长度为
的序列 ,支持 次单点修改,每次求出顺次经过 的最短合法路径。 数据范围:
。
思路分析
观察到任何一段
: 的最短路。 : 的最短路。 : 的最短路。 : 的最短路。
暴力 Dijkstra 求出边之间的全源最短路,然后对每对点预处理出这四条路径。
那么我们 dp 就只用记录
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=2005,MAXM=4005,MAXL=1e5+1,INF=1e18;
int n,m,q,len,ecnt=0,head[MAXN];
struct Edge {
int v,w,lst;
} G[MAXM];
inline void AddEdge(int u,int v,int w) { G[ecnt]={v,w,head[u]},head[u]=ecnt++; }
struct Node {
int s,t,c;
};
int dist[MAXM][MAXM];
bool vis[MAXM];
inline Node GetPath(int u,int v,int lst,int nxt) {
Node P={-1,-1,INF};
for(int s=head[u];s!=-1;s=G[s].lst) {
for(int t=head[v];t!=-1;t=G[t].lst) {
if((lst==-1||s!=lst)&&(nxt==-1||(t^1)!=nxt)&&dist[s][t^1]<P.c) {
P={s,t^1,dist[s][t^1]};
}
}
}
return P;
}
Node Info[MAXN][MAXN][4];
/* u[0],v[0]: shortest path
* u[1],v[1]: u[1]!=u[0],v[1]!=v[0]
* u[2],v[2]: u[2]!=u[0],v[2]!=v[1]
* u[3],v[3]: u[3]!=u[1],v[3]!=v[0]
*/
struct Matrix {
int f[4][4];
Matrix() { memset(f,0x3f,sizeof(f)); }
inline int* operator [](int i) { return f[i]; }
inline friend Matrix operator *(Matrix u,Matrix v) {
Matrix w;
for(int k:{0,1,2,3}) for(int i:{0,1,2,3}) for(int j:{0,1,2,3}) {
w[i][j]=min(w[i][j],u[i][k]+v[k][j]);
}
return w;
}
};
int ord[MAXL];
inline Matrix GetMatrix(int idx) {
Matrix m;
int a=ord[idx-1],b=ord[idx],c=ord[idx+1];
for(int u:{0,1,2,3}) for(int v:{0,1,2,3}) {
if(~Info[a][b][u].t&&~Info[b][c][v].s&&(Info[a][b][u].t^Info[b][c][v].s)!=1) {
m[u][v]=Info[b][c][v].c;
}
}
return m;
}
Matrix sum[MAXL<<2];
inline void Build(int l=2,int r=len-1,int pos=1) {
if(l==r) { sum[pos]=GetMatrix(l); return ; }
int mid=(l+r)>>1;
Build(l,mid,pos<<1),Build(mid+1,r,pos<<1|1);
sum[pos]=sum[pos<<1]*sum[pos<<1|1];
}
inline void Modify(int u,int l=2,int r=len-1,int pos=1) {
if(u<l||u>r) return ;
if(l==r) { sum[pos]=GetMatrix(l); return ; }
int mid=(l+r)>>1;
if(u<=mid) Modify(u,l,mid,pos<<1);
else Modify(u,mid+1,r,pos<<1|1);
sum[pos]=sum[pos<<1]*sum[pos<<1|1];
}
signed main() {
scanf("%lld%lld%lld%lld",&n,&m,&q,&len);
memset(head,-1,sizeof(head));
for(int i=1;i<=m;++i) {
int u,v,w;
scanf("%lld%lld%lld",&u,&v,&w);
AddEdge(u,v,w),AddEdge(v,u,w);
}
memset(dist,0x3f,sizeof(dist));
for(int st=0;st<ecnt;++st) {
priority_queue <array<int,2>,vector<array<int,2>>,greater<array<int,2>>> Q;
Q.push({dist[st][st]=G[st].w,st});
memset(vis,false,sizeof(vis));
while(!Q.empty()) {
int e=Q.top()[1]; Q.pop();
if(vis[e]) continue; vis[e]=true;
for(int f=head[G[e].v];f!=-1;f=G[f].lst) {
if(dist[st][f]>dist[st][e]+G[f].w&&(e^f)!=1) {
Q.push({dist[st][f]=dist[st][e]+G[f].w,f});
}
}
}
}
for(int u=1;u<=n;++u) for(int v=1;v<=n;++v) {
Info[u][v][0]=GetPath(u,v,-1,-1);
Info[u][v][1]=GetPath(u,v,Info[u][v][0].s,Info[u][v][0].t);
Info[u][v][2]=GetPath(u,v,Info[u][v][0].s,Info[u][v][1].t);
Info[u][v][3]=GetPath(u,v,Info[u][v][1].s,Info[u][v][0].t);
}
for(int i=1;i<=len;++i) scanf("%lld",&ord[i]);
if(len==2) {
while(q--) {
int x,y;
scanf("%lld%lld",&x,&y);
ord[x]=y;
printf("%lld\n",Info[ord[1]][ord[2]][0].c);
}
} else {
Build();
while(q--) {
int x,y;
scanf("%lld%lld",&x,&y);
ord[x]=y;
Modify(x-1),Modify(x),Modify(x+1);
int ans=INF;
for(int i:{0,1,2,3}) for(int j:{0,1,2,3}) {
ans=min(ans,Info[ord[1]][ord[2]][i].c+sum[1][i][j]);
}
printf("%lld\n",ans<INF?ans:-1);
}
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】