题解 P8817【[CSP-S 2022] 假期计划】
$\text{Update2022.11.8}$:我的做法居然被官方数据卡了 $5$ 分,发现一个小锅,修掉了。
To 审核:在第一次审核中,本题解因“非本题真正意义上的正解”而被拒绝,事实上,本题解第二种做法拥有正确性。本次提交还增加了正解的说明。
题意
给你一个图,$n$ 个点,$m$ 条边,两个点之间通过一次操作可达当且仅当图上存在一条不算端点,长度 $\le k$ 的路径。
每个点有点权 $s_i$,你需要选出四个互不相同的点 $a,b,c,d$ 使得相邻两个点可达且 $1$ 与 $a,d$ 都可达。
$n\le2500$,$m\le10^4$,时限 $2s$。
思路
正解怎么是折半,没想到啊。。
会卡乱搞首先要会写乱搞,所以我来提供一些乱搞做法。
记录前 $L$ 优路径
这个是我赛时做法。
首先对每个点 $\text{bfs}$ 一遍找出所有可达的点对。
考虑维护 $dp_{i,j}$ 表示第 $i$ 个点选 $j$ 的答案的最大值。但是这样子没法记录互不重复的。
考虑记录路径,但是走最优路径可能无法转移,同理,只记录前 $2$ 个等不太行。
我们考虑记录前 $L$ 优的路径和答案。转移的时候取出对方记录的路径中不含 $j$ 的方案,与目前的答案归并即可。
写完测一测,取 $L=500$,跑 $n\le 300$ 很快,但是跑 $n\le2500$ 时很慢。考虑数据分治,当 $n>2500$ 时取 $L=5$,实测能跑。实现的时候需要分开开数组,否则赋值一次过慢。
赛后实测民间数据取 $L=3$ 都能过,造得不是很强。。
时间复杂度 $O(nm+n^2Lc^2)$,其中 $c=3$,为需要选的点数。常数挺大的。
随机转移顺序
这个是 $\text{Ntokisq}$ 赛时做法。
首先对每个点 $\text{bfs}$ 一遍找出所有可达的点对。
考虑随机一个排列 $p_i$,记 $dp_{i,j}$ 表示第 $i$ 个点选 $j$ 的答案,且前 $i-1$ 个点都必须选 $p_k<p_j$ 的点 $k$ 的情况下答案的最大值。
考虑随机 $L$ 次,每次进行 $\text{dp}$,答案取最大值即可。
分析一下正确性:假设没取到答案,设答案为 $a,b,c,d$,则 $L$ 次中每次都不能出现 $p_a<p_b<p_c<p_d$ 或 $p_a>p_b>p_c>p_d$,概率为 $\left(\dfrac {11}{12}\right)^L$,则正确率为 $1-\left(\dfrac{11}{12}\right)^L$,取 $L=200$,则错误率为 $2.7\times10^{-8}$,可以接受。
时间复杂度 $O(nm+n^2Lc)$,其中 $c=4$,为选的点数。
做法 $1$ 看起来没有正确性保障(有没有神仙能证一下或者求出保证能取到答案的最小 $L$ 或者给出较小 $L$ 的 $\text{hack}$ 数据),做法 $2$ 是极高概率正确。
正解
看起来不写这个过不了审核啊。
考虑第一二个点和第三四个点的地位相同,只枚举一半。
处理出 $f_{i,1/2/3}$ 表示枚举的第二个点选 $i$,第 $1/2/3$ 大的答案和相应的转移位置。处理前三大是因为可能 $c,d$ 会与 $a,b$ 中的任意一到两个重合。
枚举 $b,c$,直接计算,时间复杂度 $O(nm+n^2)$。
做法 $1$ 的代码(也是我的考场代码,修正了一个小锅):
#include<bits/stdc++.h>
using namespace std;
//cyffff
//[70,100]+100+[60,100-]+36=[266,336-]
#define ll long long
//read(),write()
const int K=500+10,N=2500+10,M=1e4+10;
int n,m,k,bx,dis[N],cnt,head[N],L;
ll v[N],ans;
vector<int>lk[N];
struct Edge{
int to,nxt;
}a[M<<1];
struct node{
int s,a[5];
ll val;
inline void clear(){
s=val=0;
}
inline friend bool operator<(const node &a,const node &b){
return a.val>b.val;
}
inline void pushback(int p){
a[++s]=p,val+=v[p];
}
inline bool check(int p){
for(int i=1;i<=s;i++)
if(a[i]==p) return 0;
return 1;
}
inline void print(){
printf("%d %d\n",s,val);
for(int i=1;i<=s;i++)
printf("%d ",a[i]);puts("");
}
}st[K*2];
struct DPT{
node t[510];
int sz;
inline void clear(){
sz=0;
}
inline void pushback(node tmp){
t[++sz]=tmp;
}
inline void ad(int x){
for(int i=1;i<=sz;i++)
t[i].pushback(x);
}
inline void print(){
for(int i=1;i<=sz;i++)
t[i].print();
puts("---");
}
inline friend DPT operator+(DPT a,DPT b){
int top=0;
int i=1,j=1;
while(i<=a.sz&&j<=b.sz){
if(!b.t[j].check(bx)){
j++;continue;
}
if(a.t[i]<b.t[j]) st[++top]=a.t[i],i++;
else st[++top]=b.t[j],j++;
}
while(i<=a.sz) st[++top]=a.t[i],i++;
while(j<=b.sz){
if(!b.t[j].check(bx)) j++;
else st[++top]=b.t[j],j++;
}
DPT C;
C.sz=min(top,L);
for(int i=1;i<=C.sz;i++)
C.t[i]=st[i];
return C;
}
}dp[2][310];
struct DPT2{
node t[15];
int sz;
inline void clear(){
sz=0;
}
inline void pushback(node tmp){
t[++sz]=tmp;
}
inline void ad(int x){
for(int i=1;i<=sz;i++)
t[i].pushback(x);
}
inline void print(){
for(int i=1;i<=sz;i++)
t[i].print();
puts("---");
}
inline friend DPT2 operator+(DPT2 a,DPT2 b){
int top=0;
int i=1,j=1;
while(i<=a.sz&&j<=b.sz){
if(!b.t[j].check(bx)){
j++;continue;
}
if(a.t[i]<b.t[j]) st[++top]=a.t[i],i++;
else st[++top]=b.t[j],j++;
}
while(i<=a.sz) st[++top]=a.t[i],i++;
while(j<=b.sz){
if(!b.t[j].check(bx)) j++;
else st[++top]=b.t[j],j++;
}
DPT2 C;
C.sz=min(top,L);
for(int i=1;i<=C.sz;i++)
C.t[i]=st[i];
return C;
}
}dp2[2][N];
inline void add(int u,int v){
cnt++;
a[cnt].to=v;
a[cnt].nxt=head[u];
head[u]=cnt;
}
inline void bfs(int x){
for(int i=1;i<=n;i++)
dis[i]=2e9;
queue<int>q;
q.push(x);
dis[x]=0;
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=head[x];i;i=a[i].nxt){
int t=a[i].to;
if(dis[t]>dis[x]+1){
dis[t]=dis[x]+1;
if(dis[t]<=k)
q.push(t);
}
}
}
for(int i=1;i<=n;i++)
if(dis[i]<=k&&i!=x)
lk[x].push_back(i);
}
int main(){
// freopen("holiday.in","r",stdin);
// freopen("holiday.out","w",stdout);
n=read(),m=read(),k=read()+1;
if(n<=300) L=500;
else L=5;
for(int i=2;i<=n;i++)
v[i]=read();
for(int i=1;i<=m;i++){
int u=read(),v=read();
add(u,v),add(v,u);
}
for(int i=1;i<=n;i++)
bfs(i);
if(n<=300){
for(auto x:lk[1]){
node tmp;
tmp.clear();
tmp.pushback(x);
dp[1][x].pushback(tmp);
}
for(int t=2;t<=4;t++){
int c=t&1;
for(int i=2;i<=n;i++){
dp[c][i].clear();
bx=i;
for(auto j:lk[i])
dp[c][i]=dp[c][i]+dp[!c][j];
dp[c][i].ad(i);
}
}
for(auto x:lk[1])
if(dp[0][x].sz)//小锅:sz=0 时不需访问第一位
ans=max(ans,dp[0][x].t[1].val);
}else{
for(auto x:lk[1]){
node tmp;
tmp.clear();
tmp.pushback(x);
dp2[1][x].pushback(tmp);
}
for(int t=2;t<=4;t++){
int c=t&1;
for(int i=2;i<=n;i++){
dp2[c][i].clear();
bx=i;
for(auto j:lk[i])
dp2[c][i]=dp2[c][i]+dp2[!c][j];
dp2[c][i].ad(i);
}
}
for(auto x:lk[1])
if(dp2[0][x].sz)//同上
ans=max(ans,dp2[0][x].t[1].val);
}
write(ans);
}
做法 $2$ 的代码($\text{Ntokisq}$ 考场代码,事实上这份代码不是稳过,需要改成运行 $T=80$ 次或者卡时 $1.8s$ 之类的):
#include<bits/stdc++.h>
#define Yukinoshita namespace
#define Yukino std
#define ll long long
using Yukinoshita Yukino;
vector<int> a[2505],nd[2505];
bool t[2505];
ll v[2505];
int n,cnt;
int p[2505];
ll dp[2505][5],mx;
void init(int d,int k)
{
queue<pair<int,int> > q;
q.push({d,0});
memset(t,0,n+1),t[d]=1;
while(q.size())
{
pair<int,int> x=q.front();
q.pop();
if(x.first!=d)
nd[d].push_back(x.first);
if(x.second==k) continue;
for(int i=0;i<a[x.first].size();i++)
if(!t[a[x.first][i]])
t[a[x.first][i]]=1,q.push({a[x.first][i],x.second+1});
}
}
void solve()
{
int i,j,k,x;
for(i=1;i<=n;i++)
memset(dp[i],128,sizeof(dp[i]));
cnt+=n*4;
dp[1][0]=0;
for(i=2;i<=n;i++)
for(x=p[i],j=1;j<=4;j++)
{
for(k=0;k<nd[x].size();k++)
dp[x][j]=max(dp[x][j],dp[nd[x][k]][j-1]);
dp[x][j]+=v[x];
cnt+=nd[x].size();
}
for(i=0;i<nd[1].size();i++)
mx=max(mx,dp[nd[1][i]][4]);
}
int main()
{
srand(time(0));
// freopen("holiday.in","r",stdin);
// freopen("holiday.out","w",stdout);
int m,k,x,y,i,j;
scanf("%d%d%d",&n,&m,&k);
for(i=2;i<=n;i++)
scanf("%lld",&v[i]);
while(m--)
scanf("%d%d",&x,&y),
a[x].push_back(y),
a[y].push_back(x);
for(i=1;i<=n;i++)
init(i,k+1);
for(i=1;i<=n;i++)
p[i]=i;
solve();
reverse(p+2,p+1+n);
solve();
while(cnt<1e8*1.5)
random_shuffle(p+2,p+1+n),solve();
cout<<mx;
}
没有看出来正解,还是水平不太足,当然,乱搞能力也是一种能力。
再见 qwq~

浙公网安备 33010602011771号