[BZOJ3560]DZY Loves Math V 题解
题目地址:BZOJ:Problem 3560. — DZY Loves Mat …
May all the beauty be blessed.
题目地址:洛谷:【P2599】[ZJOI2009]取石子游戏 – 洛谷、BZOJ:Problem 1413. — [ZJOI2009]取石子游戏
在研究过Nim游戏及各种变种之后,Orez又发现了一种全新的取石子游戏,这个游戏是这样的: 有n堆石子,将这n堆石子摆成一排。游戏由两个人进行,两人轮流操作,每次操作者都可以从最左或最右的一堆中取出若干颗石子,可以将那一堆全部取掉,但不能不取,不能操作的人就输了。 Orez问:对于任意给出一个初始一个局面,是否存在先手必胜策略。
输入格式:
文件的第一行为一个整数T,表示有 T组测试数据。对于每组测试数据,第一行为一个整数n,表示有n堆石子;第二行为n个整数ai,依次表示每堆石子的数目。
输出格式:
对于每组测试数据仅输出一个整数0或1。其中1表示有先手必胜策略,0表示没有。
输入样例#1:
1 4 3 1 9 4
输出样例#1:
0
数据范围
对于30%的数据 n≤5 ai≤10^5
对于100%的数据 T≤10 n≤1000 每堆的石子数目≤10^9
我并不能保证底下的题解完全正确,欢迎各位同学阅读的时候自己想一想找找错,毕竟我在想这个题的时候也被绕进去了。包括底下的这个参考资料,我觉得也都有点问题。
参考资料:题解 P2599 【[ZJOI2009]取石子游戏】 – Jason_Yvan 的博客 – 洛谷博客
这里有一种做法,定义lft[i][j]表示(i, j)区间的左边加一堆该数量的石子先手必败,rgt[i][j]表示(i, j)区间的右边加一堆该数量的石子先手必败,我们利用这两个数组的递推关系进行区间DP,来判定每个局面的状况。这是由于局面之间是可以相互转移的。
设计这个定义的合法性基于对于一个区间,lft和rgt的值是唯一确定的。下面给出证明:
首先,类似Nim游戏,我们会发现当只有两堆石子且数量相等的时候,先手必败。因此初始值即lft[i][i] = rgt[i][i] = a[i]。
剩下的是一个区间DP的思路,我们枚举每个区间,进行转移。转移可以对状况分类讨论。
先分析求lft[i][j],令L = lft[i][j – 1],R = rgt[i][j – 1],X = a[j]:
rgt[i][j]仍可以用类似的方法来求。令L = lft[i + 1][j],R = rgt[i + 1][j],X = a[i]:
最后验证a[1]是否与lft[2][n]相等即可。复杂度O(Tn^2)。
// Code by KSkun, 2018/6
#include <cstdio>
#include <cctype>
#include <algorithm>
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();
for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
return res * neg;
}
const int MAXN = 1005;
int T, n, a[MAXN], lft[MAXN][MAXN], rgt[MAXN][MAXN];
int main() {
T = readint();
while(T--) {
n = readint();
for(int i = 1; i <= n; i++) {
a[i] = lft[i][i] = rgt[i][i] = readint();
}
for(int k = 1; k <= n; k++) {
for(int i = 1; i + k <= n; i++) {
int j = i + k, L = lft[i][j - 1], R = rgt[i][j - 1], X = a[j];
if(R == X) lft[i][j] = 0;
else if(L > X && R > X) lft[i][j] = X;
else if(L <= X && R > X) lft[i][j] = X + 1;
else if(L > X && R < X) lft[i][j] = X - 1;
else lft[i][j] = X;
L = lft[i + 1][j]; R = rgt[i + 1][j]; X = a[i];
if(L == X) rgt[i][j] = 0;
else if(R > X && L > X) rgt[i][j] = X;
else if(R <= X && L > X) rgt[i][j] = X - 1;
else if(R > X && L < X) rgt[i][j] = X + 1;
else rgt[i][j] = X;
}
}
puts(a[1] == lft[2][n] ? "0" : "1");
}
return 0;
}
题目地址:BZOJ:Problem 3551. — [ONTAK2010]Peaks加强版
在Bytemountains有N座山峰,每座山峰有他的高度h_i。有些山峰之间有双向道路相连,共M条路径,每条路径有一个困难值,这个值越大表示越难走,现在有Q组询问,每组询问询问从点v开始只经过困难值小于等于x的路径所能到达的山峰中第k高的山峰,如果无解输出-1。
有一个图,点和边都有权值。回答若干询问,每个询问表示只保留图中边权不大于x的边,v所在的连通块中,点权k大。强制在线。
输入格式:
第一行三个数N,M,Q。
第二行N个数,第i个数为h_i
接下来M行,每行3个数a b c,表示从a到b有一条困难值为c的双向路径。
接下来Q行,每行三个数v x k,表示一组询问。v=v xor lastans,x=x xor lastans,k=k xor lastans。如果lastans=-1则不变。
输出格式:
对于每组询问,输出一个整数表示答案。
输入样例#1:
10 11 4 1 2 3 4 5 6 7 8 9 10 1 4 4 2 5 3 9 8 2 7 8 10 7 1 4 6 7 1 6 4 8 2 1 5 10 8 10 3 4 7 3 4 6 1 5 2 7 3 0 0 4 9 9 8 3
输出样例#1:
6 1 -1 8
【数据范围】
N<=10^5, M,Q<=5*10^5,h_i,c,x<=10^9。
参考资料:BZOJ 3551: [ONTAK2010]Peaks加强版 [Kruskal重构树 dfs序 主席树] – Candy? – 博客园
强制在线之前的离线做法就不行了。这里要用新的科技:Kruskal重构树。
首先,连通块的边肯定在最小生成树上比较优,因此我们可以跑一波Kruskal处理。在Kruskal选中一条边的时候,对这条边开一个点,把边权放在点上,再从点引出两条边,指向边的端点的并查集集合代表元,将这条边的点设为这两个集合的并集代表元。
这样建出来的树有优秀的性质:
这里我们会用到第5条。一个查询就是在找v的边权不大于询问的最浅边点祖先,在该祖先的子树内找k大,这个显然可以DFS序建主席树做。
注意本地开够栈,DFS的规模会相当大。复杂度是O(n \log n)。
// Code by KSkun, 2018/6
#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();
for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
return res * neg;
}
const int MAXN = 200005, MAXM = 500005;
int n, m, q, N;
int fa[MAXN];
inline int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
struct Node {
int lch, rch, val;
} tr[MAXN << 4];
int rt[MAXN], tot;
inline void insert(int &o, int l, int r, int x) {
tr[++tot] = tr[o]; o = tot;
tr[o].val++;
if(l == r) return;
int mid = (l + r) >> 1;
if(x <= mid) insert(tr[o].lch, l, mid, x);
else insert(tr[o].rch, mid + 1, r, x);
}
inline int query(int o1, int o2, int l, int r, int k) {
if(l == r) return l;
int mid = (l + r) >> 1, rsiz = tr[tr[o2].rch].val - tr[tr[o1].rch].val;
if(k <= rsiz) return query(tr[o1].rch, tr[o2].rch, mid + 1, r, k);
else return query(tr[o1].lch, tr[o2].lch, l, mid, k - rsiz);
}
int w[MAXN], anc[MAXN][20];
std::vector<int> gra[MAXN];
struct Edge {
int u, v, w;
} edges[MAXM];
inline bool cmp(Edge a, Edge b) {
return a.w < b.w;
}
inline void kruskal() {
int cnt = 0;
for(int i = 1; i <= m; i++) {
int u = edges[i].u, v = edges[i].v, fu = find(u), fv = find(v);
if(fu == fv) continue;
w[++N] = edges[i].w;
gra[N].push_back(fu); gra[N].push_back(fv);
fa[fu] = fa[fv] = fa[N] = N;
if(++cnt == n - 1) break;
}
}
int vn[MAXN], dep[MAXN], dfl[MAXN], dfr[MAXN], clk;
inline void dfs(int u) {
dfl[u] = clk;
if(u <= n) vn[++clk] = u;
for(int i = 1; (1 << i) <= dep[u]; i++) {
anc[u][i] = anc[anc[u][i - 1]][i - 1];
}
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
if(v == anc[u][0]) continue;
dep[v] = dep[u] + 1;
anc[v][0] = u;
dfs(v);
}
dfr[u] = clk;
}
inline int findrt(int u, int x) {
for(int i = 19; i >= 0; i--) {
if(anc[u][i] && w[anc[u][i]] <= x) u = anc[u][i];
}
return u;
}
std::vector<int> tmp;
int main() {
n = readint(); m = readint(); q = readint(); N = n;
for(int i = 1; i <= n; i++) {
fa[i] = i;
}
tmp.push_back(-1);
for(int i = 1; i <= n; i++) {
w[i] = readint();
tmp.push_back(w[i]);
}
for(int i = 1; i <= m; i++) {
edges[i].u = readint(); edges[i].v = readint(); edges[i].w = readint();
}
std::sort(edges + 1, edges + m + 1, cmp);
kruskal();
dfs(N);
std::sort(tmp.begin(), tmp.end());
tmp.erase(std::unique(tmp.begin(), tmp.end()), tmp.end());
for(int i = 1; i <= n; i++) {
w[i] = std::lower_bound(tmp.begin(), tmp.end(), w[i]) - tmp.begin();
}
for(int i = 1; i <= n; i++) {
rt[i] = rt[i - 1];
insert(rt[i], 1, n, w[vn[i]]);
}
int lastans = 0, v, x, k;
while(q--) {
v = readint() ^ lastans; x = readint() ^ lastans; k = readint() ^ lastans;
v = findrt(v, x);
if(tr[rt[dfr[v]]].val - tr[rt[dfl[v]]].val < k) puts("-1"), lastans = 0;
else printf("%d\n", lastans = tmp[query(rt[dfl[v]], rt[dfr[v]], 1, n, k)]);
}
return 0;
}
题目地址:BZOJ:Problem 2430. — [Poi2003]Chocolate
有一块n*m的矩形巧克力,准备将它切成n*m块。巧克力上共有n-1条横线和m-1条竖线,你每次可以沿着其中的一条横线或竖线将巧克力切开,无论切割的长短,沿着每条横线切一次的代价依次为y1,y2,…,yn-1,而沿竖线切割的代价依次为x1,x2,…,xm-1。例如,对于下图6*4的巧克力,
我们先沿着三条横线切割,需要3刀,得到4条巧克力,然后再将这4条巧克力沿竖线切割,每条都需要5刀,则最终所花费的代价为y1+y2+y3+4*(x1+x2+x3+x4+x5)。
当然,上述简单切法不见得是最优切法,那么怎样切割该块巧克力,花费的代价最少呢?
要把一个大矩形横着切n-1刀竖着切m-1刀分成n*m个小矩形,从左到右从上到下每个位置切一刀都有一个代价,每一次对一整块(不论大小,无法一刀切断多块)切一次都会产生一次代价,求切割的最小代价。
输入格式:
第一行为两个整数n和m。
接下来n-1行,每行一个整数,分别代表x1,x2,…,xn-1。
接下来m-1行,每行一个整数,分别代表y1,y2,…,ym-1。
输出格式:
输出一整数,为切割巧克力的最小代价。
输入样例#1:
6 4 2 1 3 1 4 4 1 2
输出样例#1:
42
30%的数据,n<=100,m<=100
100%的数据,n<=10000,m<=10000
贪心。
一定是先切掉代价较大的位置。考虑证明它,对于平行的刀切位置,在一个序列中先切代价大的,由于每一刀要乘上与位置垂直的位置已经切了几刀这个数字,因此越靠前乘的数字越小,总和越小。对于垂直的两个刀切位置,先切代价较大的那个,若先切较小的,较大的乘的数字会+1,比先切较大的更劣。
由于需要排序,复杂度O(n \log n)。
// Code by KSkun, 2018/6
#include <cstdio>
#include <cctype>
#include <algorithm>
#include <vector>
#include <functional>
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();
for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
return res * neg;
}
const int MAXN = 20005;
struct Node {
int w;
bool type;
inline bool operator>(const Node &rhs) const {
return w > rhs.w;
}
};
int n, m;
std::vector<Node> vec;
int main() {
n = readint(); m = readint();
for(int i = 1, t; i < n; i++) {
t = readint(); vec.push_back(Node {t, 0});
}
for(int i = 1, t; i < m; i++) {
t = readint(); vec.push_back(Node {t, 1});
}
std::sort(vec.begin(), vec.end(), std::greater<Node>());
int ch = 1, cv = 1, ans = 0;
for(int i = 0; i < vec.size(); i++) {
ans += vec[i].w * (vec[i].type ? ch : cv);
if(vec[i].type) cv++; else ch++;
}
printf("%d", ans);
return 0;
}