[SDOI2009]晨跑 题解
题目地址:洛谷:【P2153】[SDOI2009]晨跑 – 洛谷、BZOJ:P …
May all the beauty be blessed.
题目地址:洛谷:【P2519】[HAOI2011]problem a – 洛谷、BZOJ:Problem 2298. — [HAOI2011]problem a
一次考试共有n个人参加,第i个人说:“有ai个人分数比我高,bi个人分数比我低。”问最少有几个人没有说真话(可能有相同的分数)
输入格式:
第一行一个整数n,接下来n行每行两个整数,第i+1行的两个整数分别代表ai、bi
输出格式:
一个整数,表示最少有几个人说谎
输入样例#1:
3 2 0 0 2 2 2
输出样例#1:
1
100%的数据满足: 1≤n≤100000 0≤ai、bi≤n
不好求最少说谎人数,转而求最大没说谎人数。
考虑说谎是怎么说的:
第一个可以在读入的时候顺手处理了,而第二个需要用动态规划。
把每个前面有a人,后面有b人的信息处理成成绩所在的区间为[a+1, n-b]这样的形式,并统计这个区间上实际有多少人,问题就变成了,每个区间有一个权值,选择不重叠的区间让权值最大。这个可以用DP做,令dp[i]表示选择完[1, i]这里面的区间后,权值和最大的值。对于右端点i,我可以不选区间,则从dp[i-1]转移而来;选择区间,则从选择的区间的左边转移而来,也就是说
\displaystyle dp[i] = \max \begin{cases} dp[i-1] & \text{不选择右端点为i的区间} \\ dp[\text{选择区间左端点}-1] + \text{选择区间的权值} & \text{选择右端点为i的区间} \end{cases}
我们用一个vector数组,vec[i]表示右端点为i的区间的左端点集合,这样就可以遍历该vector实现转移了。而权值的统计,实际上是输入数据中每个区间的出现次数,用个map搞一搞。注意如果权值超过区间长度,要跟区间长度取个min。
学习了一波题解 P2519 【[HAOI2011]problem a】 – rushcheyo 的博客 – 洛谷博客的写法,感谢这位同学。
// Code by KSkun, 2018/5
#include <cstdio>
#include <cctype>
#include <algorithm>
#include <vector>
#include <map>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1;
register char c = fgc();
while(!isdigit(c)) {
if(c == '-') neg = -1;
c = fgc();
}
while(isdigit(c)) {
res = (res << 1) + (res << 3) + c - '0';
c = fgc();
}
return res * neg;
}
const int MAXN = 100005;
int n;
struct Node {
int s, t;
inline bool operator<(const Node &rhs) const {
return t == rhs.t ? s < rhs.s : t < rhs.t;
}
};
std::vector<int> vec[MAXN];
std::map<Node, int> cnt;
int dp[MAXN];
int bef, aft;
int main() {
n = readint();
for(int i = 1; i <= n; i++) {
bef = readint(); aft = readint();
if(bef + aft + 1 > n) continue;
if(++cnt[Node {bef + 1, n - aft}] == 1) {
vec[n - aft].push_back(bef + 1);
}
}
for(int i = 1; i <= n; i++) {
dp[i] = dp[i - 1];
for(int j = 0; j < vec[i].size(); j++) {
dp[i] = std::max(dp[i],
dp[vec[i][j] - 1] + std::min(i - vec[i][j] + 1, cnt[Node {vec[i][j], i}]));
}
}
printf("%d", n - dp[n]);
return 0;
}
题目地址:洛谷:【P2396】yyy loves Maths VII – 洛谷
yyy对某些数字有着情有独钟的喜爱,他叫他们为幸运数字;然而他作死太多,所以把自己讨厌的数字成为”厄运数字”
一群同学在和yyy玩一个游戏
每次,他们会给yyy n张卡片,卡片上有数字,所有的数字都是”幸运数字”,我们认为第i张卡片上数字是ai
每次yyy可以选择向前走ai步并且丢掉第i张卡片
当他手上没有卡片的时候他就赢了
但是呢,大家对”厄运数字”的位置布置下了陷阱,如果yyy停在这个格子上,那么他就输了
(注意:即使到了终点,但是这个位置是厄运数字,那么也输了)
现在,有些同学开始问:
yyy有多大的概率会赢呢?
大家觉得这是个好问题
有人立即让yyy写个程序
“电脑运行速度很快!24的阶乘也不过就620448401733239439360000,yyy你快写个程序来算一算”
yyy表示很无语,他表示他不想算概率,最多算算赢的方案数,而且是%1,000,000,007以后的值
大家都不会写程序,只好妥协
但是这时候yyy为难了,24!太大了,要跑好长时间.
他时间严重不够!需要你的帮助!
由于yyy人格分裂,某个数字可能既属于幸运数字又属于厄运数字。
输入格式:
第一行n
下面一行n张卡片
第三行m 表示yyy的厄运数字个数(最多2个)
最后一行是m个厄运数字
输出格式:
方案数%1,000,000,007
输入样例#1:
8 1 3 1 5 2 2 2 3 0
输出样例#1:
40320
输入样例#2:
24 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 10 15
输出样例#2:
0
数据范围:
10%的数据n<=10
50%的数据n<=23
100%的数据n<=24
勉强算可以状压吧。
考虑状压DP统计方案数,方程比较容易想出
\displaystyle dp[S] = \sum_{i \in S, i \neq \text{厄运}} dp[S \backslash \{i\}]
我们预处理出数组dis[S]表示S这些卡片的和,方便判断是否可以转移。
复杂度是O(2^n)。然而很卡常,所以还是开O2吧23333。
// Code by KSkun, 2018/5
#include <cstdio>
#include <cctype>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1;
register char c = fgc();
while(!isdigit(c)) {
if(c == '-') neg = -1;
c = fgc();
}
while(isdigit(c)) {
res = (res << 1) + (res << 3) + c - '0';
c = fgc();
}
return res * neg;
}
const int MAXN = (1 << 24) + 5, MO = 1e9 + 7;
inline int lowbit(int x) {
return x & -x;
}
int n, m, num[5], dis[MAXN], dp[MAXN];
int main() {
n = readint();
for(register int i = 1; i <= n; i++) {
dis[1 << (i - 1)] = readint();
}
m = readint();
for(register int i = 1; i <= m; i++) {
num[i] = readint();
}
for(register int i = 1; i < 1 << n; i++) {
register int j = lowbit(i);
dis[i] = dis[i ^ j] + dis[j];
}
dp[0] = 1;
for(register int i = 1; i < 1 << n; i++) {
register bool can = true;
for(register int j = 1; j <= m; j++) {
if(dis[i] == num[j]) {
can = false; break;
}
}
if(!can) continue;
for(register int j = i, k = lowbit(j); j; j -= k, k = lowbit(j)) {
dp[i] += dp[i ^ k];
if(dp[i] >= MO) dp[i] -= MO;
}
}
printf("%d", dp[(1 << n) - 1]);
return 0;
}
题目地址:洛谷:【P3644】[APIO2015]八邻旁之桥 – 洛谷、BZOJ:Problem 4071. — [Apio2015]巴邻旁之桥
一条东西走向的穆西河将巴邻旁市一分为二,分割成了区域 A 和区域 B。
每一块区域沿着河岸都建了恰好 1000000001 栋的建筑,每条岸边的建筑都从 0 编号到 1000000000。相邻的每对建筑相隔 1 个单位距离,河的宽度也是 1 个单位长度。区域 A 中的 i 号建筑物恰好与区域 B 中的 i 号建筑物隔河相对。
城市中有 N 个居民。第 i 个居民的房子在区域 Pi 的 Si 号建筑上,同时他的办公室坐落在 Qi 区域的 Ti 号建筑上。一个居民的房子和办公室可能分布在河的两岸,这样他就必须要搭乘船只才能从家中去往办公室,这种情况让很多人都觉得不方便。为了使居民们可以开车去工作,政府决定建造不超过 K 座横跨河流的大桥。
由于技术上的原因,每一座桥必须刚好连接河的两岸,桥梁必须严格垂直于河流,并且桥与桥之间不能相交。
当政府建造最多 K 座桥之后,设 Di 表示第 i 个居民此时开车从家里到办公室的最短距离。请帮助政府建造桥梁,使得 D1+D2+⋯+DN 最小。
输入格式:
输入的第一行包含两个正整数 K 和 N,分别表示桥的上限数量和居民的数量。
接下来 N 行,每一行包含四个参数:Pi,Si,Qi 和 Ti,表示第 i 个居民的房子在区域 Pi 的 Si 号建筑上,且他的办公室位于 Qi 区域的 Ti 号建筑上。
输出格式:
输出仅为一行,包含一个整数,表示 D1+D2+⋯+DN 的最小值。
输入样例#1:
1 5 B 0 A 4 B 1 B 3 A 5 B 7 B 2 A 6 B 1 A 7
输出样例#1:
24
输入样例#2:
2 5 B 0 A 4 B 1 B 3 A 5 B 7 B 2 A 6 B 1 A 7
输出样例#2:
22
所有数据都保证:Pi 和 Qi 为字符 “A” 和 “B” 中的一个, 0≤Si,Ti≤1000000000,同一栋建筑内可能有超过 1 间房子或办公室(或二者的组合,即房子或办公室的数量同时大于等于 1)。
子任务 1 (8 分) K=1 1≤N≤1000
子任务 2 (14 分) K=1 1≤N≤100000
子任务 3 (9 分) K=2 1≤N≤100
子任务 4 (32 分) K=2 1≤N≤1000
子任务 5 (37 分) K=2 1≤N≤100000
由于位置坐标比较大,先离散化这些位置。首先对于同侧的情况,建桥对其无影响,单独统计加入答案即可。
注意到K只有1和2两种取值,分开考虑。
K=1的时候,直接将桥建在所有家和办公室位置的中间(中位数)即可,这个位置的结果一定最优。
K=2的时候,考虑将这些线段分成两部分,以线段中点作为区分,中点较小的一部分走一座桥,另一部分走另一座桥。我们可以枚举排序后[1, i]内的线段属于一部分,其他属于另一部分,分别求中点计算答案。
计算中点可以考虑使用权值线段树来处理。上述算法的复杂度是O(N \log N)。
但是非常可惜,直接做会爆long long,这里用一点小trick可以避免答案过大,详情参见代码。
// Code by KSkun, 2018/5
#include <cstdio>
#include <cctype>
#include <algorithm>
#include <vector>
typedef long long LL;
inline char fgc() {
static char buf[100000], *p1 = buf, *p2 = buf;
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF
: *p1++;
}
inline LL readint() {
register LL res = 0, neg = 1;
register char c = fgc();
while(!isdigit(c)) {
if(c == '-') neg = -1;
c = fgc();
}
while(isdigit(c)) {
res = (res << 1) + (res << 3) + c - '0';
c = fgc();
}
return res * neg;
}
inline bool isAB(char c) {
return c == 'A' || c == 'B';
}
inline char readAB() {
char c;
while(!isAB(c = fgc()));
return c;
}
const int MAXN = 200005;
std::vector<LL> tmp;
struct SegmentTree {
#define lch o << 1
#define rch o << 1 | 1
#define mid ((l + r) >> 1)
LL val[MAXN << 2], sum[MAXN << 2];
inline void insert(int o, int l, int r, int x) {
val[o]++; sum[o] += tmp[x];
if(l == r) return;
if(x <= mid) insert(lch, l, mid, x);
else insert(rch, mid + 1, r, x);
}
inline void erase(int o, int l, int r, int x) {
val[o]--; sum[o] -= tmp[x];
if(l == r) return;
if(x <= mid) erase(lch, l, mid, x);
else erase(rch, mid + 1, r, x);
}
inline int queryrk(int o, int l, int r, int rk) {
if(l == r) return l;
int lsiz = val[lch];
if(rk <= lsiz) return queryrk(lch, l, mid, rk);
else return queryrk(rch, mid + 1, r, rk - lsiz);
}
inline LL querysum(int o, int l, int r, int ll, int rr) {
if(l >= ll && r <= rr) return sum[o];
LL res = 0;
if(ll <= mid) res += querysum(lch, l, mid, ll, rr);
if(rr > mid) res += querysum(rch, mid + 1, r, ll, rr);
return res;
}
inline LL querysiz(int o, int l, int r, int ll, int rr) {
if(l >= ll && r <= rr) return val[o];
LL res = 0;
if(ll <= mid) res += querysiz(lch, l, mid, ll, rr);
if(rr > mid) res += querysiz(rch, mid + 1, r, ll, rr);
return res;
}
} tr1, tr2;
int k, n, N;
LL ans;
struct Node {
LL s, t;
inline bool operator<(const Node &rhs) const {
return s + t < rhs.s + rhs.t;
}
};
std::vector<Node> info;
inline int main1() {
LL sum1 = 0, sum2 = 0;
for(int i = 1; i <= N / 2; i++) {
sum1 += tmp[i];
}
for(int i = N / 2 + 1; i <= N; i++) {
sum2 += tmp[i];
}
printf("%lld", ans + sum2 - sum1);
return 0;
}
inline int main2() {
if(!info.size()) {
printf("%lld", ans); return 0;
}
std::sort(info.begin(), info.end());
for(int i = 0; i < info.size(); i++) {
info[i].s = std::lower_bound(tmp.begin(), tmp.end(), info[i].s) - tmp.begin();
info[i].t = std::lower_bound(tmp.begin(), tmp.end(), info[i].t) - tmp.begin();
}
for(int i = 0; i < info.size(); i++) {
tr1.insert(1, 1, N, info[i].s); tr1.insert(1, 1, N, info[i].t);
}
LL mn = 1e18;
for(int i = 0; i < info.size(); i++) {
tr1.erase(1, 1, N, info[i].s); tr1.erase(1, 1, N, info[i].t);
tr2.insert(1, 1, N, info[i].s); tr2.insert(1, 1, N, info[i].t);
LL m1 = tr1.queryrk(1, 1, N, tr1.val[1] / 2),
m2 = tr2.queryrk(1, 1, N, tr2.val[1] / 2),
d1 = tmp[m1] * tr1.querysiz(1, 1, N, 1, m1) - tr1.querysum(1, 1, N, 1, m1)
+ tr1.querysum(1, 1, N, m1, N) - tmp[m1] * tr1.querysiz(1, 1, N, m1, N),
d2 = tmp[m2] * tr2.querysiz(1, 1, N, 1, m2) - tr2.querysum(1, 1, N, 1, m2)
+ tr2.querysum(1, 1, N, m2, N) - tmp[m2] * tr2.querysiz(1, 1, N, m2, N);
mn = std::min(mn, d1 + d2);
}
printf("%lld", ans + mn);
return 0;
}
LL si, ti;
char pi, qi;
int main() {
k = readint(); n = readint();
for(int i = 1; i <= n; i++) {
pi = readAB(); si = readint(); qi = readAB(); ti = readint();
if(si > ti) {
std::swap(pi, qi); std::swap(si, ti);
}
if(pi == qi) ans += ti - si;
else ans++, info.push_back(Node {si, ti});
}
tmp.push_back(-1);
for(int i = 0; i < info.size(); i++) {
tmp.push_back(info[i].s);
tmp.push_back(info[i].t);
}
std::sort(tmp.begin(), tmp.end());
N = tmp.size() - 1;
if(k == 1) return main1();
tmp.erase(std::unique(tmp.begin(), tmp.end()), tmp.end());
N = tmp.size() - 1;
return main2();
}