#qbxt国庆水题记#
#day2#
//100 + 0 + 0 = 100
//除了超水题以外其他的都不会
##Problem 1. video##
Input file: video.in
Output file: video.out
Time limit: 1s
Memory limit: 256M
pluto 喜欢看片,现在他的硬盘里有 n 部片,但是由于他还要把妹,所以看片时间有限,他只能挑出
其中的 k 部片来看,他想知道有多少种不同的选片方案。方案数可能很大,答案 mod 1000000007 输出。
Input
输入文件第一行一个整数 N, 表示这个星球上的总人口。
接下来 N 行,每行一个正整数,表示每个居民的姓名。
Output
输出文件一行一个整数,表示这个星球的价值。
Example
video.in
6 2
video.out
15
Scoring
• 对于 30% 的数据,n ≤10
• 对于 60% 的数据,n ≤3000
• 对于 100% 的数据,n ≤2 ×105
排列组合
根据公式求阶乘,在求下逆元即可
代码
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
const int inf = 1000000007;
int n,m;
ll p,q;
void exgcd(ll a,ll b,ll &d,ll &x,ll &y) {
if(!b) {d = a, x = 1, y = 0;return;}
exgcd(b,a%b,d,y,x);
y -= x * (a / b);
}
ll inv(ll a, ll b) {
ll d,x,y;
exgcd(a,b,d,x,y);
return (x % b + b) % b;
}
int main() {
freopen("video.in","r",stdin);
freopen("video.out","w",stdout);
cin>>n>>m;
m = min(m, n-m);
p = 1, q = 1;
for(int i = 1; i <= m; i++) p = (p * i) % inf;
q = inv(p,inf);
for(int i = n; i >= n - m + 1; i--) q = (q * i) % inf;
cout<<q<<endl;
return 0;
}
##Problem 2. chance##
Input file: chance.in
Output file: chance.out
Time limit: 1s
Memory limit: 256M
pluto 去找妹子 ×××约会,然后×××要求和 pluto 玩一个游戏,pluto 赢了才能获得和 ×××
约会的机会。游戏内容为:现在有 N 个袋子 (你可以认为它是哆啦 A 梦的口袋,每个袋里面放着一些
,所以容量十分大,第 i 个袋里面放着编号为 Li 到 Ri 的球 (除编号外完全相同),pluto 需要从每个
袋里面摸出一个球,第 i 个袋子任何一个球被摸到的概率是 1/(Ri −Li + 1),如果 pluto 摸出的球中
有 K% 或以上的球的编号的第一位是 1(比如 11,121,199 的第一位是 1, 而 21,233 第一位就不是 1),那
么 pluto 就将赢得与 ×××约会的机会。现在 pluto 想知道他能人生中第一次与妹子约会的概率有多大。
Input
第一行两个整数 N, K
接下来 N 行,每个两个整数,Li 和 Ri
Output
一行一个实数(保留 7 位⼩数)表示答案
绝对误差不超过 10−6 即视为正确
Example
chance.in
2 50
1 2
9 11
chance.out
0.833333333333333
Scoring
• 对于 100% 的数据,0 ≤k ≤100,0 < Li ≤Ri
• 对于 30% 的数据,n ≤10,Li ≤Ri ≤100
• 对于 60% 的数据,n ≤500,Li ≤Ri ≤2000
• 对于 100% 的数据,n ≤2000,Li ≤Ri ≤1018
首先判断出每个袋子里拿到第一个数字为1的球的概率为pi(很巧妙的判断见"pd()")
dp[i][j] 表示前i个袋子里取到了j个第一个数字为1的球的概率
dp[i][j] = dp[i - 1][j - 1] * pi + dp[i - 1][j] * (1 - pi)
然后根据K%求出至少要多少人,相加即可
//为什么这么多dp
//啊啊啊啊啊
代码
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn = 2000 + 100;
int n;
long long l,r;
double k,pi[maxn],f[maxn][maxn];
long long pd(long long x) {
long long num = 1,sum = 1;
if(!x) return 0;
for(int i = 2; i <= 20; i++) {
num *= 10;
if(num > x) return sum;
if(num * 2 > x) return sum + x - num + 1;
sum += num;
}
}
int pp(double p) {
if(p - (int)p == 0) return (int)p;
else{
return (int)(p + 1);
}
}
int main() {
freopen("chance.in","r",stdin);
freopen("chance.out","w",stdout);
cin>>n>>k;
for(int i = 1; i <= n; i++) {
scanf("%lld%lld",&l,&r);
pi[i] = (double)(pd(r) - pd(l-1)) / (double)(r - l + 1);
}
f[0][0] = 1;
for(int i = 1; i <= n; i++) {
for(int j = 0; j <= i; j++) {
f[i][j] += f[i-1][j] * (1 - pi[i]);
if(j) f[i][j] += f[i-1][j-1] * pi[i];
}
}
k /= 100;
int t = pp(n*k);
double ans = 0;
for(int i = t; i <= n; i++) {
ans += f[n][i];
}
printf("%.7f\n",ans);
return 0;
}
##Problem 3. plutotree##
Input file: plutotree.in
Output file: plutotree.out
Time limit: 2s
Memory limit: 256M
有一棵 n 个节点的树,节点编号为 1 到 n,i 号节点的权值为 Wi。这棵树有些奇怪,它的每一个
叶子节点都是根节点的夫亲 (表示每个叶子节点与根节点之间有一条边权为 0 的边)。我们称这样的树为
pluto 树,根节点编号为 1。我们需要最小化从 u 到 v 的路径 (每条边只能经过一次) 上的节点权值之和,
并且在最小化节点权值之和的同时求这个路径上可能的最大权值。
Input
第一行两个整数 n 和 q,n 表示节点个数,q 表示询问个数。
第二行 n −1 个整数 Ai,表示 i + 1 号节点的夫亲为 Ai
第三行 n 个整数 Wi 表示 i 号节点的权值为 Wi
接下来 q 行,每行两个整数 u,v,表示一组询问
Output
对于每组询问输出两个整数 x, y
x 表⽰ u 到 v 的权值和最小的路径的权值和,y 表⽰这条路径上点权最大值。如果有多个相同权值
和的路径,输出那个点权最大值最大的。
Example
plutotree.in
5 1
1 2 3 4
413 127 263 869 960
1 5
plutotree.out
1373 960
Scoring
• 对于 30% 的数据,n ≤300,q ≤1000
• 对于 50% 的数据,n ≤2000,q ≤10000
• 对于 100% 的数据,n ≤100000,q ≤100000
//树上dp
//不会,暴力也不会打
来自题解
30% 把图建建出来 floyd
60% 把floyd换成spfa或者dijkstra
100% 树形dp
首先答案可能有三种情况:
1.不走叶子到根的边, 那就是一个简单的树上的问题, 两点之间有且仅有一条路径,树上倍增一下就可以把两个问题都解决, 或者闲着蛋疼的可以用树剖
2.从u走到叶子,从叶子到根,从根到v (这种情况要把u、v调换再做一次)
3.从u走到叶子,从叶子到根,从根到另一个叶子节点,再从叶子节点到v
对于第二种、三种情况, 其实关键在于求出每个点到离他最近的叶子的路径,这样的路径有两种可能,一种是向下下走到叶子, 一种是先向上(有可能经过根也可能不经过)走,最终到达叶子
首先树形dp出每个节点向下到离它最近的叶子节点的距离,并同时记录这个路径上的最大值,设为down[i], (down[i]为二元组,<路径权值和, 路径上最大权值>) down[i] 可以从 i号节点的所有儿子转移过来, 转移方程很显然
有了down[i], 就可以dp出我们需要的每个点到最近的叶子节点的路径了,将这个记为dp[i], dp[i]同样是一个二元组,和down[i]一样。 首先dp[1]我们是知道的,就是从根直接走到某一个叶子节点, 其余的dp[i]初值都是down[i], dp[i]可以从i号节点的父亲转移过来,所以这是一个自上而下的dp
考虑到可能爆栈的情况, 建议使用bfs处理树形dp
具体状态如何转移可以参考std
代码std
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 111111;
const int inf = 1 << 20;
int fa[N][20], n, m, maximum[N][20], leaf[N], weight[N];
int dist[N],dep[N];
pair<int, int> dp[N], down[N];
vector<int> adj[N];
void update(pair<int, int> &x, pair<int, int> y) {
if (x.first != y.first) {
x = min(x, y);
} else {
x = max(x, y);
}
}
void build() {
vector<int> queue;
queue.push_back(1);
fa[1][0] = 0;
dep[1] = 1;
dist[1] = weight[1];
for (int head = 0; head < (int)queue.size(); head++) {
int now = queue[head];
for (int i = 0; i < (int)adj[now].size(); i++) {
queue.push_back(adj[now][i]);
dep[adj[now][i]] = dep[now] + 1;
dist[adj[now][i]] = dist[now] + weight[adj[now][i]];
}
}
for (int j = 1; j <= 18; j++) {
for (int i = 1; i <= n; i++) {
fa[i][j] = fa[fa[i][j - 1]][j - 1];
maximum[i][j] = max(maximum[i][j - 1], maximum[fa[i][j - 1]][j - 1]);
}
}
for (int i = (int)queue.size() - 1; i >= 0; i--) {
int x = queue[i];
update(down[fa[x][0]], make_pair(down[x].first + weight[fa[x][0]], max(down[x].second, weight[fa[x][0]])));
}
}
void DP() {
vector<int> queue;
queue.push_back(1);
update(dp[1], down[1]);
for (int head = 0; head < (int)queue.size(); head++) {
int now = queue[head];
for (int i = 0; i < (int)adj[now].size(); i++) {
int to = adj[now][i];
dp[to] = down[to];
update(dp[to], make_pair(dp[now].first + weight[to], max(weight[to], dp[now].second)));
queue.push_back(to);
}
}
}
pair<int, int> query(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
int fx = x, fy = y;
int maxd;
int ret1 = 0, ret2 = 0;
for (maxd = 0; (1 << maxd) <= dep[x]; maxd++);
maxd--;
ret1 = max(maximum[x][0], maximum[y][0]);
for (int i = maxd; i >= 0; i--) {
if (dep[fa[x][i]] >= dep[y]) {
ret1 = max(ret1, maximum[x][i]);
x = fa[x][i];
}
}
ret1 = max(ret1, maximum[x][0]);
ret1 = max(ret1, maximum[y][0]);
if (x == y) {
ret2 = dist[fx] - dist[fy] + weight[fy];
return make_pair(ret1, ret2);
}
for (int i = maxd; i >= 0; i--) {
if (fa[x][i] != 0 && fa[x][i] != fa[y][i]) {
ret1 = max(ret1, maximum[x][i + 1]);
ret1 = max(ret1, maximum[y][i + 1]);
x = fa[x][i];
y = fa[y][i];
}
}
ret1 = max(ret1, maximum[x][0]);
ret1 = max(ret1, maximum[y][0]);
ret1 = max(ret1, maximum[fa[x][0]][0]);
ret2 = dist[fx] + dist[fy] - dist[fa[x][0]] * 2 + weight[fa[x][0]];
return make_pair(ret1, ret2);
}
void add(int x, int y) {
adj[x].push_back(y);
}
void solve() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) adj[i].clear();
for (int i = 1; i <= n - 1; i++) {
int x;
scanf("%d", &x);
add(x, i + 1);
fa[i + 1][0] = x;
}
for (int i = 1; i <= n; i++) {
if (adj[i].size() == 0) leaf[i] = 1;
else leaf[i] = 0;
}
for (int i = 1; i <= n; i++) {
scanf("%d", &weight[i]);
maximum[i][0] = weight[i];
}
for (int i = 1; i <= n; i++) {
dp[i] = make_pair(inf, 0);
down[i] = make_pair(inf, 0);
}
for (int i = 1; i <= n; i++) {
if (leaf[i]) {
down[i] = make_pair(weight[i], weight[i]);
update(dp[1], make_pair(weight[1] + weight[i], max(weight[1], weight[i])));
}
}
build();
DP();
for (int i = 1; i <= m; i++) {
pair<int, int> tmp, ans;
int u, v;
scanf("%d%d", &u, &v);
tmp = query(u, v);
ans = make_pair(tmp.second, -tmp.first);
tmp = make_pair(dp[u].first + dist[v], -max(dp[u].second, maximum[v][18]));
ans = min(ans, tmp);
tmp = make_pair(dp[v].first + dist[u], -max(dp[v].second, maximum[u][18]));
ans = min(ans, tmp);
tmp = make_pair(dp[u].first + dp[v].first + weight[1], -max(max(dp[u].second, dp[v].second), weight[1]));
ans = min(ans, tmp);
printf("%d %d\n", ans.first, -ans.second);
}
}
int main() {
freopen("plutotree.in", "r", stdin);
freopen("plutotree.out", "w", stdout);
int tests = 1;
for (int i = 1; i <= tests; i++) {
solve();
}
return 0;
}