坦夫稼轩(互测赛)题解
A.永遇乐
出题人谢罪:
本题原本的出题意图是考察分层图和虚点,但是发现两个都不需要……现在看来是
所以能跑过就行,大家能写出做法也很强,下面是参考做法,仅供参考。
题意
求在无向图上从
思路
考虑把在点上停留也转化成边,自然想到分层图。
建两层图,每层内连无向边,再把
为了快速求出答案,可以将第二层的
代码
#include<iostream>
#include<vector>
#include<queue>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N=1e5+10;
const int maxs=1e15;
int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){ if(ch=='-') w=-1; ch=getchar();}
while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar();}
return s*w;
}
int n,m,ka,kb,d[N*2];
priority_queue <pii > q;
bool f[N*2];//因为建了两层图,所以要开两倍空间
struct edg
{
int v,w;
};
vector <edg> edge[N*2];//同上
void add(int u,int v,int w)
{
edg t;
t.w=w,t.v=v;
edge[u].push_back(t);
}//加边操作
signed main()
{
n=read(),m=read();
for(int i=1;i<=m;i++)
{
int u=read(),v=read(),w=read();
add(u,v,w),add(v,u,w);
add(n+u,n+v,w),add(n+v,n+u,w);//层内建边
}
ka=read(),kb=read();
for(int i=1;i<=ka;i++)
{
int t=read(),ta=read();
add(t,n+t,ta);//层间建边,即为停留的代价
}
for(int i=1;i<=kb;i++)
{
int t=read();
add(n+t,0,0);//连向虚点
}
for(int i=0;i<=2*n;i++) d[i]=maxs,f[i]=0;
d[1]=0;//初始化
q.push({0,1});
while(!q.empty())
{
int u=q.top().second; q.pop();
if(f[u]) continue;
f[u]=1;
for(int i=0;i<edge[u].size();i++)
{
int ne=edge[u][i].v,tw=edge[u][i].w;
if(d[ne]>d[u]+tw)
{
d[ne]=d[u]+tw;
q.push({-d[ne],ne});
}
}
}//Dijkstra
cout<<d[0];
return 0;
}
B.贺新郎
题意
给出
思路
看数据范围显然是状压dp,这里可以把题意抽象成在平面直角坐标系中有 当然这里不转化也是状压dp)。
代码
#include<iostream>
#include<cmath>
#define int long long
using namespace std;
const int N=18+10;
const int M=3e5+10;
int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){ if(ch=='-') w=-1; ch=getchar();}
while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar();}
return s*w;
}
int n,ans=-1,x[N],y[N];
int s[N][N];//存储跳跃度
int f[N][M];//f[i][j]表示以i结尾,j状态可达到的最大效果值
int dis(int a,int b)
{
return abs(x[a]-x[b])+abs(y[a]-y[b]);
}
signed main()
{
n=read();
for(int i=1;i<=n;i++) x[i]=read();
for(int i=1;i<=n;i++) y[i]=read();
for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++)
s[i][j]=dis(i,j),s[j][i]=s[i][j];
for(int i=1;i<=n;i++) f[i][1<<(i-1)]=0;//可从任意部分开始,但其实在这份代码里这个初始化没用
for(int k=1;k<(1<<n);k++) for(int i=1;i<=n;i++)//枚举状态和最终点
{
if(k&(1<<(i-1))==0) continue;
for(int j=1;j<=n;j++)//枚举上一个点
{
if(i==j||(k&(1<<(j-1)))==0) continue;
f[i][k]=max(f[i][k],f[j][k-(1<<(i-1))]+s[j][i]);//转移
}
}
for(int i=0;i<n;i++) ans=max(ans,f[i][(1<<n)-1]);//可在任意部分结束,记录答案
cout<<ans;
return 0;
}
C.青玉案
(没想到吧,这题是原题P4042,是出题人在qbxt做到的题)
(虽然原题是紫,但个人觉得大概是蓝)
题意
每个烟花可以公开放,这样会得到若干个烟花并增加热闹值;也可以自己放,只会增加热闹值。求最小热闹值。
思路
若设 显然有
显然仅当
(其实好像还有一点拓扑的思想,即只有一种烟花会产生的所有烟花都处理完,才能处理该烟花对应式子的第二项)
代码
此处代码中的
#include<iostream>
#include<vector>
#include<queue>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N=2e5+10;
int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){ if(ch=='-') w=-1; ch=getchar();}
while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar();}
return s*w;
}
int n,a[N],b[N],s[N],f[N],ans[N];
bool v[N];
vector <int> edge[N];//存储产生烟花情况
priority_queue <pii > q;
signed main()
{
n=read();
for(int i=1;i<=n;i++)
{
a[i]=read(),b[i]=read(),s[i]=read();
for(int j=1;j<=s[i];j++)
{
int t=read();
edge[t].push_back(i);//由产生的烟花连向该烟花
}
q.push({-b[i],i});//初始把每个烟花都以直接放完的代价放入队列
f[i]=a[i];//初值设为公开燃放的代价
}
while(!q.empty())
{
int u=q.top().second,w=-q.top().first; q.pop();
if(v[u]) continue;
v[u]=1,ans[u]=w;//记录答案
for(int i=0;i<edge[u].size();i++)
{
int ne=edge[u][i];
if(v[ne]||f[ne]>b[ne]) continue;
f[ne]+=w;//此处就会将所有f_v_j加到f中
s[ne]--;//记录产生烟花中未处理的数量
if(s[ne]==0) q.push({-f[ne],ne});//若全部处理完则再加入堆
}
}//Dijkstra思想
cout<<ans[1];
return 0;
}
D.破阵子
(类似于P1395,由于把树这个信息坑人地藏了起来,同时点有点权,评了绿)
题意
给定一棵树(因为每两个地区之间有且仅有一条路线,可以确定为一棵树,即保证
思路
(既然说了是树那显然是树形dp了吧)
典型换根dp,先钦定 显然有
然后再计算以每个点为集中点的总代价
这样再dfs一遍同时更新答案最小值即可。
代码
#include<iostream>
#include<vector>
#define int long long
using namespace std;
const int N=2e5+10;
const int maxs=1e15;
int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){ if(ch=='-') w=-1; ch=getchar();}
while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar();}
return s*w;
}
int n,m,tot=0;
vector <int> edge[N];
int a[N],sum[N],cnt[N],ans[N],minn=maxs;
void dfs(int u,int fat)
{
sum[u]=0,cnt[u]=a[u];
for(int i=0;i<edge[u].size();i++)
{
int ne=edge[u][i];
if(ne==fat) continue;
dfs(ne,u);
cnt[u]+=cnt[ne],sum[u]+=(sum[ne]+cnt[ne]);
}
}//第一遍dfs记录cnt和sum
void dfsb(int u,int fat)
{
minn=min(minn,ans[u]);
for(int i=0;i<edge[u].size();i++)
{
int ne=edge[u][i];
if(ne==fat) continue;
ans[ne]=ans[u]+tot-cnt[ne]*2;
dfsb(ne,u);
}
}//第二遍dfs记录答案
signed main()
{
n=read(),m=read();
for(int i=1;i<=n;i++) a[i]=read(),tot+=a[i];
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
edge[u].push_back(v),edge[v].push_back(u);
}
dfs(1,0);
ans[1]=sum[1];
dfsb(1,0);
cout<<minn;
return 0;
}
E.水调歌头
(分组背包裸题,可参照P1757)
(这题应该是最简单的吧,板子是橙,由于需要自己处理组别,所以我评了黄)
闲聊
这里不讲题意之类的了,只说下部分分,话说真的有人不会吗),
(其实此题
std代码
#include<iostream>
using namespace std;
const int N=4e3+10;
const int K=4e5+10;
const int M=9e2+10;//最多只有(1207-1140+1)*12=816个组
int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){ if(ch=='-') w=-1; ch=getchar();}
while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar();}
return s*w;
}
int n,k,l=1e9,r=-1e9; //这里记录的是下标最大和下标最小的组
int cnt[M],w[M][N],v[M][N],f[K];
int main()
{
n=read(),k=read();
for(int i=1;i<=n;i++)
{
int y=read(),m=read(),tw=read(),tv=read();
int gr=(y-1140)*12+m;//这里转化一下标号,方便存储
cnt[gr]++,l=min(l,gr),r=max(r,gr);//更新组数的范围
w[gr][cnt[gr]]=tw,v[gr][cnt[gr]]=tv;//分组存储
}
for(int i=l;i<=r;i++) for(int j=k;j>=0;j--)//枚举每个组和最终的容量
{
bool tf=true;
for(int tk=1;tk<=cnt[i];tk++) if(j-w[i][tk]>=0)//枚举每件物品
tf=false,f[j]=max(f[j],f[j-w[i][tk]]+v[i][tk]);
if(tf) break;//若所有物品都无法更新,则跳出循环(这里不剪枝会慢很多,但能过)
}
cout<<f[k];
return 0;
}