(联考)noip71
虽然考的很烂,但blog还是要写的。
T1
考场想法:最优方案显然是让这个点的每条边的颜色均匀分布,于是我就去给每条边染色去了,大样例死活过不去,然后我就爆蛋了。
好吧,还有可怜我的5pts。
正解:
由Vizing定理可知,每个点的度数为模 \(c\) 不为0,则会对最后答案产生1的贡献,求个和即可。
啥是Vizing定理和证明?我不会,所以丢blog
sb结论题
不过确实写出来的人很多,签到题三字都写在题面上了,然而我还是上来就跳了这道题...
就算不知道这个定理,自己模几组还是能推出来的。
还是懒了,本来就菜,还懒,只能说活该。
对于一些没思路的题,可以花一些时间去模几组样例,总之就是不能懒。
T2
考场想法:实在没啥好说的,因为是暴力,部分分也没拿着。
正解:
很简单,根据新加入的 \(u,v\) 的祖孙关系分类讨论,用线段树维护即可。
这里默认 \(v\) 的深度更大。
不难发现,输入的 \(u,v\) 只有两种关系:
- 如果 \(u,v\) 存在祖孙关系,那么画个图就可以发现,能产生贡献的就是 \(u\) 的子树,和除了 \(v\) 在这条链的儿子的子树(不算点 \(u\) 的子树)之外的点。
- 如果 \(u,v\) 没有祖孙关系,那么能产生贡献的点一定是在两个点的子树中。
所以只需要求个dfn序,再写个支持区间加+全局max的线段树即可。
对于1,需要找到 \(v\) 在这条链上的儿子,可以用倍增来实现。
注意题目要求,离总祭祀台最近的只能有一个,且只能是 \(u,v\) 中的一个。
考场上多次在正解门前徘徊,但终究是没有前进。
好吧,其实就是菜....
没思路的题可以画个图,模下样例。
根据一些性质进行分类讨论也是比较重要的解题思路。
T3
考场想法:题目好长,干脆弃了。
正解:
SPT+树形dp。
SPT:Shortest Path Tree ,最短路树,即记录下来最短路转移过程中 \(u\) 的前驱 \(pre_{u}\) ,然后对于所有的节点都以其 \(pre\) 为父亲节点建树,就出来了SPT。
有题目可得,点 \(u\) 仅能由其前驱过来,并且点1到其他节点的最短路只有一条,所以考虑SPT+树形dp。
先跑遍 \(dijkstra\) ,然后建SPT,在SPT上树形dp。
设 \(dp_{u,j}\) 表示以 \(u\) 为根的子树总共放了 \(j\) 个警察,能抓住杨吞天的最大概率。
转移前先做遍子树合并,点 \(u\) 的转移枚举点 \(u\) 放多少个警察和其子树放多少个警察即可。
Code
#include<queue>
#include<cstdio>
#include<cctype>
#include<cstring>
#define re register
const int N = 303;
const int M = 3e4+3;
#define scanf oma=scanf
using std::priority_queue;
int oma;
namespace some
{
struct stream
{
template<typename type>inline stream &operator >>(type &s)
{
bool w=0; s=0; char ch=getchar();
while(!isdigit(ch)){ w|=ch=='-'; ch=getchar(); }
while(isdigit(ch)){ s=(s<<1)+(s<<3)+(ch^48); ch=getchar(); }
return s=w?-s:s,*this;
}
}cin;
int n,m,k;
double p[N][N],ans;
template<typename type>inline type max(type a,type b)
{ return a>b?a:b; }
#define debug(s) debug((char*)s)
auto debug = [](char* s) { printf("%s\n",s); };
}using namespace some;
namespace Graph
{
struct graph
{
int next;
int to;
int w;
}edge[M<<1];
struct SPT
{
int next;
int to;
}tree[M<<1];
int cnt=1,head[N];
auto add = [](int u,int v,int w) -> void
{ edge[++cnt] = (graph){head[u],v,w},head[u] = cnt; };
auto link = [](int u,int v) -> void
{ tree[++cnt] = (SPT){head[u],v},head[u] = cnt; };
struct my
{
int id,dis;
inline bool friend operator <(const my &u,const my &v)
{ return u.dis>v.dis; }
};
bool vis[N];
int dis[N],pre[N];
priority_queue<my>q;
auto dijkstra = []() -> void
{
memset(dis,0x3f,sizeof(dis));
q.push((my){1,dis[1] = 0});
while(!q.empty())
{
int u = q.top().id; q.pop();
if(vis[u])
{ continue ; }
vis[u] = true;
for(re int i=head[u],v,w; i; i=edge[i].next)
{
v = edge[i].to,w = edge[i].w;
if(dis[v]>dis[u]+w)
{
dis[v] = dis[u]+w,pre[v] = u;
q.push((my){v,dis[v]});
}
}
}
};
int du[N];
double dp[N][N],bag[N];
void dfs(int u)
{
if(!head[u])
{
for(re int i=1; i<=k; i++)
{ dp[u][i] = p[u][i]; }
return ;
}
for(re int i=head[u]; i; i=tree[i].next)
{ dfs(tree[i].to); }
memset(bag,0,sizeof(bag));
for(re int i=head[u]; i; i=tree[i].next)
{
for(re int j=k; ~j; j--)
{
for(int x=0; x<=j; x++)
{ bag[j] = max(bag[j],bag[j-x]+dp[tree[i].to][x]); }
}
}
for(re int i=1; i<=k; i++)
{ bag[i] /= 1.0*du[u]; }
for(re int i=1; i<=k; i++)
{
dp[u][i] = bag[i];
for(re int j=0; j<=i; j++)
{ dp[u][i] = max(dp[u][i],bag[j]*(1-p[u][i-j])+p[u][i-j]); }
}
}
}using namespace Graph;
namespace OMA
{
auto main = []() -> signed
{
freopen("arrest.in","r",stdin); freopen("arrest.out","w",stdout);
cin >> n >> m >> k;
for(re int i=1,u,v,w; i<=m; i++)
{ cin >> u >> v >> w; add(u,v,w),add(v,u,w); }
dijkstra();
cnt = 1; memset(head,0,sizeof(head));
for(re int i=2; i<=n; i++)
{ link(pre[i],i); du[pre[i]]++; }
for(re int i=1; i<=n; i++)
{
for(re int j=1; j<=k; j++)
{ scanf("%lf",&p[i][j]); }
}
dfs(1);
//debug("fuck\n");
/*for(re int i=1; i<=n; i++)
{
for(re int j=1; j<=k; j++)
{ printf("%0.6lf ",dp[i][j]); }
printf("\n");
}*/
printf("%0.6lf\n",dp[1][k]);
return 0;
};
}
signed main()
{ return OMA::main(); }
T4
考场想法:式子好长好ex,弃了弃了
正解:
sb测试点分治
其实看数据范围后,就应该想到sb测试点分治,然而我直接弃了。
所以考场上如何推出这样的式子???
打表yyds!
这场考试对待后两题的态度不对,畏难心理导致跳题,一些该拿的部分分也没有拿到。
前两题比较简单,但仍然没拿多少分,一方面思考方向有问题,另一方面还是自己懒,前两题都可以模样例模出来,但自己还是没有写出来...
还是菜,一些比较普通的套路,和思考方向都没有,多积累一下把...