题目

题解(有些小错误)

H老爷的简短题解

请无视题目 $pdf$ 的第二行,信那句话的人都已经上清华了

听说大老爷切了 $250+$ 分,然后发现是两个人分着写三道题的,然后第一题还流假了…… $xswl$


T1

$10\%,\space n\le 16$

枚举

$20\%,\space K\le 10$

发现影响每个位置的决策只有前 $k$ 个,将每相邻 $k$ 个位置压缩成一个二进制状态,做 $DP$。

$20\%,\space Q=0$

分数规划?

强行钦定初始时全部选择正立表演,第 $i$ 个位置若有贡献则要付出 $A_i-B_i$ 的代价(说白了,一个位置如果要把正立表演换成倒立表演就有贡献,否则无贡献),那么每连续 $K$ 个节目中至少有 $P$ 个贡献。

列出 $N-K+1$ 个等式,第 $k$ 个等式为

$$(\sum_{k=i}^{k+K-1}C_k)-Y_i = P \space\space i∈[1,n-K+1],\space Y_i∈[0,K-P-Q]$$

其中 $C_i$ 表示第 $i$ 个位置是否贡献,$Y_i$ 表示 $[k,k+K-1]$ 这个节目段中那些无表演限制的节目中有多少个选择了贡献。

添加第 $0$ 个等式和第 $N-K+2$ 个等式,式子为 $0=0$。然后我们就可以差分了,把每相邻两个式子作差得到:

T2

$50\%,\space n\le 5000$

由于 $50$ 分做法比较显然,我就没考虑 $20$ 分的……给的题解说是枚举两种深度建虚树……

首先看到这种题会有一种直觉:$dis(a,b)=dis(a,c)=dis(b,c)$ 等价于 $dis(a,o)=dis(b,o)=dis(c,o)$,其中 $o$ 是树上一点。

也就是说,我们可以枚举树上一个中点,然后以这个点为根,找三个不同子树中的深度相同的点(即三个点到这个根的距离相等),这三个点就是组成一个合法的无序三元组。

这样会不会漏掉一些三元组?其实不会。

我们从好想的两个点入手证明:

我们要添加一个点 $c$,使得 $dis(a,c)=dis(b,c)$。$dis(a,b)$ 不用考虑,如果 $dis(a,c)=dis(b,c)$ 这个条件能满足的话,让它们两个都等于 $dis(a,c)$ 就行了($dis(a,c)$ 和 $dis(b,c)$ 达不到 $dis(a,b)$ 的情况可以被判掉,先不考虑)。

因为是在树上,所以 $a,b,c$ 三点只能有一条简单路径连接。

如果 $c$ 接在 $a$ 左边,$dis(b,c)$ 就必定大于 $dis(a,c)$,从而无法满足题目要求 $dis(a,b)=dis(a,c)=dis(b,c)$。

$c$ 接在 $b$ 右边同理。

所以 $c$ 只能从 $a,b$ 的简单路径上叉出去。

这时 $dis(a,b)=dis(a,c)=dis(b,c)$ 是有可能的。

设叉出的那个点为 $o$,因为 $dis(a,c)=dis(b,c)$,所以 $dis(a,c)-dis(o,c)=dis(b,c)-dis(o,c)$,$dis(a,o)=dis(b,o)$

同理推出 $dis(a,o)=dis(c,o)$

所以必定存在一个点 $o$ 满足 $dis(a,o)=dis(b,o)=dis(c,o)$, 我们只要枚举这个点 $o$ 即可。

对于枚举的一个点 $o$,设这个点为树根,满足 $dis(a,o)=dis(b,o)=dis(c,o)$ 的点 $a,b,c$ 一定分别在三棵不同子树中(否则在同一子树的两个点的简单路径不经过点 $o$,比如点 $a,c$ 在同一子树中,那这两点的最短路就不是 $dis(a,o)+dis(o,c)$,无法用以上证明来证明当前枚举点是这三点对应的中点 $o$)。

可以发现,同一深度的所有点的组合方案数可以直接列式计算(深度就是一个点到树根 $o$ 的距离)。

由于一开始我理解错题意了,写成了求所有有序三元组的贡献和……不过根据三个数有 $6$ 种排列的性质,可知答案除以 $6$ 就是所有无序三元组的贡献和……所以我直接说有序三元组的贡献和的求法了。

考虑枚举一个深度的一个点(其实就 $dfs$ 搜一下就可以了)。对于这个点,它可以跟其它所有子树的所有同深度的点组成三元组,我们把这个点固定在三元组的第一位,后两位就一定是其它子树中同深度的点了。以后找那些同深度的点时,就会把这个点放在三元组的第二位、第三位,再算一遍贡献相同、顺序不同的三元组。由于每个点都会因此被固定在三个位置各一次,所以可以证明这样一定能算全所有方案。

但是我们显然不可能再用一个或两个循环枚举其它子树中同深度的点,因为现在的复杂度已经是 $O(n^2)$ 了。

我们回头观察一下一个三元组的贡献:$V_a\times V_b + V_a\times V_c + V_b\times V_c$

其实到这里已经可以做了,因为三项是相加,可以拆开算,每项都可以结合预处理做到 $O(1)$ 计算,把三个总和加起来就是固定当前点在第一位时,所有三元组的贡献和了。

题外话:

但我为了少算点东西,取了个巧……

既然同一个三元组会以不同顺序计算 $6$ 次,我们可以换位思考,将一个三元组的贡献改为 $3\times (V_b\times V_c)$,这样这个三元组被以不同顺序计算 $6$ 次后,贡献的总和与原来相同?

这样就只需要算 $V_b\times V_c$,不用预处理其它子树中同深度的所有点的权值和了。

code(取模写得丑见谅哈)

 #include<bits/stdc++.h>
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define dwn(i,x,y) for(int i=(x);i>=(y);--i)
#define rep_e(i,u) for(int i=hd[u];i;i=e[i].nxt)
#define ll long long
#define int long long
#define N 5005
#define mod 998244353
#define inv_6 166374059
using namespace std;
inline int read(){
int x=; bool f=; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=;
for(; isdigit(c);c=getchar()) x=(x<<)+(x<<)+(c^'');
if(f) return x;
return -x;
}
int n,V[N];
struct edge{int v,nxt;}e[N<<];
int hd[N],cnt;
inline void add(int u,int v){e[++cnt]=(edge){v,hd[u]}, hd[u]=cnt;}
int c[N][N],c_sum[N],self_pf[N],son_num,cnt_three[N];
bool vis[N],three[N][N];
void dfs(int u,int fa,int dis){
(c[son_num][dis]+=V[u])%=mod, (c_sum[dis]+=V[u])%=mod;
if(!three[son_num][dis]) three[son_num][dis]=, ++cnt_three[dis];
//printf("dfs:%d %d %d\n",u,dis,V[u]);
rep_e(i,u) if(e[i].v!=fa) dfs(e[i].v,u,dis+);
}
int mxDis;
void getPf(int u,int fa,int dis){
if(!vis[dis]) (self_pf[dis]+=c[son_num][dis]*c[son_num][dis]%mod)%=mod, vis[dis]=, mxDis=max(mxDis,dis);
rep_e(i,u) if(e[i].v!=fa) getPf(e[i].v,u,dis+);
}
int ans;
void getAns(int u,int fa,int dis){
if(cnt_three[dis]>=){
int k=(c_sum[dis]*c_sum[dis]%mod-self_pf[dis]-c[son_num][dis]*(c_sum[dis]-c[son_num][dis])*%mod+mod)%mod;
//printf("%d : %d %d %d %d %d\n",u,dis,c_sum[dis]*c_sum[dis],self_pf[dis]*2,c[son_num][dis],c[son_num][dis]*(c_sum[dis]-c[son_num][dis])*2);
//cout<<k<<endl;
(ans+=k*%mod)%=mod;
}
rep_e(i,u) if(e[i].v!=fa) getAns(e[i].v,u,dis+);
}
void clear(int u,int fa,int dis){
c[son_num][dis]=c_sum[dis]=cnt_three[dis]=self_pf[dis]=, three[son_num][dis]=;
rep_e(i,u) if(e[i].v!=fa) clear(e[i].v,u,dis+);
}
signed main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
n=read();
int u,v;
rep(i,,n) u=read(), v=read(), add(u,v), add(v,u);
rep(i,,n) V[i]=read();
rep(i,,n){
//printf("start:%d\n",i);
son_num=;
rep_e(j,i) ++son_num, dfs(e[j].v,i,);
son_num=;
rep_e(j,i) ++son_num, mxDis=, getPf(e[j].v,i,), fill(vis+,vis+mxDis+,);
son_num=;
rep_e(j,i) ++son_num, getAns(e[j].v,i,);
son_num=;
rep_e(j,i) ++son_num, clear(e[j].v,i,);
}
cout<<ans*inv_6%mod<<endl;
return ;
}

$50\%,\space n\le 5000$

群众:蛤,怎么还是 $50$ 分?

别着急,这里用的是推 $dp$ 方程的方法,可以引出正解(我写的 $50$ 分就是小学生做法没什么拓展性)

$100\%$

前置知识:长链剖分

【2019.3.2】NOI 模拟赛的更多相关文章

  1. 『2019/4/9 TGDay2模拟赛 反思与总结』

    2019/4/9 TGDay2模拟赛 今天是\(TG\)模拟赛的第二天了,试题难度也是相应地增加了一些,老师也说过,这就是提高组的难度了.刚开始学难的内容,一道正解也没想出来,不过基本的思路也都是对了 ...

  2. 『2019/4/8 TGDay1模拟赛 反思与总结』

    2019/4/8 TGDay1模拟赛 这次是和高一的学长学姐们一起参加的\(TG\)模拟考,虽然说是\(Day1\),但是难度还是很大的,感觉比\(18\)年的\(Day1\)难多了. 还是看一下试题 ...

  3. NOI模拟赛 Day1

    [考完试不想说话系列] 他们都会做呢QAQ 我毛线也不会呢QAQ 悲伤ING 考试问题: 1.感觉不是很清醒,有点困╯﹏╰ 2.为啥总不按照计划来!!! 3.脑洞在哪里 4.把模拟赛当作真正的比赛,紧 ...

  4. 2019.7.26 NOIP 模拟赛

    这次模拟赛真的,,卡常赛. The solution of T1: std是打表,,考场上sb想自己改进匈牙利然后wei了(好像匈牙利是错的. 大力剪枝搜索.代码不放了. 这是什么神仙D1T1,爆蛋T ...

  5. 6.28 NOI模拟赛 好题 状压dp 随机化

    算是一道比较新颖的题目 尽管好像是两年前的省选模拟赛题目.. 对于20%的分数 可以进行爆搜,对于另外20%的数据 因为k很小所以考虑上状压dp. 观察最后答案是一个连通块 从而可以发现这个连通块必然 ...

  6. 【2019.3.20】NOI模拟赛

    题目 这里必须标记一下那个傻逼问题,再不解决我人就没了! 先放一个 $T3$ $20$ 分暴力 #include<bits/stdc++.h> #define rep(i,x,y) for ...

  7. NOI 模拟赛 #2

    得分非常惨惨,半个小时写的纯暴力 70 分竟然拿了 rank 1... 如果 OYJason 和 wxjor 在可能会被爆踩吧 嘤 T1 欧拉子图 给一个无向图,如果一个边集的导出子图是一个欧拉回路, ...

  8. 【2018.12.10】NOI模拟赛3

    题目 WZJ题解 大概就是全场就我写不过 $FFT$ 系列吧……自闭 T1 奶一口,下次再写不出这种 $NTT$ 裸题题目我就艹了自己 -_-||| 而且这跟我口胡的自创模拟题 $set1$ 的 $T ...

  9. 【2019.8.15 慈溪模拟赛 T1】插头(plugin)(二分+贪心)

    二分 首先,可以发现,最后的答案显然满足可二分性,因此我们可以二分答案. 然后,我们只要贪心,就可以验证了. 贪心 不难发现,肯定会优先选择能提供更多插座的排插,且在确定充电器个数的情况下,肯定选择能 ...

随机推荐

  1. notify()和notifyAll()主要区别

    notify()和notifyAll()都是Object对象用于通知处在等待该对象的线程的方法. void notify(): 唤醒一个正在等待该对象的线程.void notifyAll(): 唤醒所 ...

  2. SummerVocation_Learning--java的线程机制

    线程:是一个程序内部的执行路径.普通程序只有main()一条路径.如下列程序: import java.lang.Thread; //导入线程实现包 public class Test_Thread ...

  3. Uva 填充正方形

    暴力出奇迹 #include<iostream> #include<cstdio> using namespace std; +; int T,n; char S[maxn][ ...

  4. js函数带括号和不带括号赋给对象属性的区别

    注意: 1.js为对象添加函数时,不要在函数后面加().一旦加了括号是表示将函数的返回值赋给对象的属性. 例:function test(){ document.writeln("我是js函 ...

  5. GTF/GFF

  6. 100个经典C语言程序(益智类)

    100个经典C语言程序(益智类) [1.绘制余弦曲线] 在屏幕上用“*”显示0~360度的余弦函数cos(x)曲线 [问题分析与算法设计] 利用cos(x)的左右对称性,将屏幕的行方向定义为x,列方向 ...

  7. Android如何添加多张引导页

    摘要:项目需要添加多张引导页,所以在网上搜集了一些资料并整理好. Step1 添加一个GuideActivity. 其实这个引导页无非就是一个Activity,里面有一个ViewPager而已.多张图 ...

  8. 读书笔记jvm探秘之二: 对象创建

    对象是面向对象设计语言无法回避的东西,可见其重要性,JAVA的对象相较于C++来说,不算很复杂,但是我们看到一句话背后往往有很多东西值得探讨(NEW关键字). 对象如何被创建? 首先一句简单的NEW语 ...

  9. Linux之匿名FTP服务器搭建

    FTP(File Transfer Protocol)是在服务器与客户端进行文件传输的一种传输协议.本次介绍的是vsftpd的软件体验ftp服务. FTP服务器默认情况下依据用户登录情况分为三种不同的 ...

  10. 【Best Time to Buy and Sell Stock】cpp

    题目: Say you have an array for which the ith element is the price of a given stock on day i. If you w ...