POI 2013 题解(长文警告)
POI 2013 Solution
- POI 2013 Solution
- 更好的阅读体验戳此进入
- 题面链接
- 另一个题面链接
- LG-P3558 [POI2013]BAJ-Bytecomputer
- LG-P3550 [POI2013]TAK-Taxis
- LG-P3560 [POI2013]LAN-Colorful Chain
- LG-P3552 [POI2013]SPA-Walk
- LG-P3556 [POI2013]MOR-Tales of seafaring
- LG-P3563 [POI2013]POL-Polarization
- LG-P3551 [POI2013]USU-Take-out
- LG-P3553 [POI2013]INS-Inspector
- LG-P3557 [POI2013]GRA-Tower Defense Game
- LG-P3554 [POI2013]LUK-Triumphal arch
- LG-P3547 [POI2013]CEN-Price List
- LG-P3562 [POI2013] LAS-Laser
- LG-P3548 [POI2013] GOB-Tapestries
- LG-P3559 [POI2013] LAB-Maze
- LG-P3549 [POI2013]MUL-Multidrink
- UPD
更好的阅读体验戳此进入
(建议您从上方链接进入我的个人网站查看此 Blog,在 Luogu 中图片会被墙掉,部分 Markdown 也会失效)
题面链接
另一个题面链接
LG-P3558 [POI2013]BAJ-Bytecomputer
题面
给定长度为 BRAK
。
Examples
Input_1
6 -1 1 0 -1 0 1
Output_1
3
Solution
一道绿色但我感觉还挺有意思的 DP。
这个数据范围这个题意,是一道 DP 应该很显然吧。。。
首先我们可以贪心地想到,为了使操作次数尽可能地少,最终操作完的数列应该还是均为
然后我们就可以考虑设置状态
然后分类讨论第
switch(a[i]){
case -1:{
dp[i][-1] = dp[i - 1][-1];
// dp[i][0] = dp[i - 1][1] + 1;
dp[i][1] = dp[i - 1][1] + 2;
break;
}
case 0:{
dp[i][-1] = dp[i - 1][-1] + 1;
dp[i][0] = min(dp[i - 1][-1], dp[i - 1][0]);
dp[i][1] = dp[i - 1][1] + 1;
break;
}
case 1:{
dp[i][-1] = dp[i - 1][-1] + 2;
dp[i][0] = dp[i - 1][-1] + 1;
dp[i][1] = min({dp[i - 1][-1], dp[i - 1][0], dp[i - 1][1]});
break;
}
default:break;
}
然后聪明的你发现按照一般思路写完无法 Accept,甚至样例都过不了,简单分析一下发现,对于 Case -1 的第二行的转移显然是不合法的。
思考这个转移的含义是什么,原数列上第
Tips:对于一些空间上的优化,可以考虑通过类似背包问题二维压一维的滚动数组的优化,不过意义不大,然后为了写起来更直观,需要让数组支持访问负下标,可以写成如下形式:
int dp_r[1100000][4];
int* dp[1100000];
然后主函数里这样初始化:
memset(dp_r, 0x3f, sizeof(dp_r));
for(int i = 0; i <= 1010000; ++i)dp[i] = dp_r[i] + 1;
大概就这样。。。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
/******************************
abbr
******************************/
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define INF 0x3f3f3f3f
template<typename T = int>
inline T read(void);
int N;
int a[1100000];
int dp_r[1100000][4];
int* dp[1100000];
int main(){
memset(dp_r, 0x3f, sizeof(dp_r));
for(int i = 0; i <= 1010000; ++i)dp[i] = dp_r[i] + 1;
N = read();
for(int i = 1; i <= N; ++i)a[i] = read();
dp[1][a[1]] = 0;
for(int i = 2; i <= N; ++i){
switch(a[i]){
case -1:{
dp[i][-1] = dp[i - 1][-1];
// dp[i][0] = dp[i - 1][1] + 1;
dp[i][1] = dp[i - 1][1] + 2;
break;
}
case 0:{
dp[i][-1] = dp[i - 1][-1] + 1;
dp[i][0] = min(dp[i - 1][-1], dp[i - 1][0]);
dp[i][1] = dp[i - 1][1] + 1;
break;
}
case 1:{
dp[i][-1] = dp[i - 1][-1] + 2;
dp[i][0] = dp[i - 1][-1] + 1;
dp[i][1] = min({dp[i - 1][-1], dp[i - 1][0], dp[i - 1][1]});
break;
}
default:break;
}
}
// for(int i = 1; i <= N; ++i){
// for(int j = -1; j <= 1; ++j){
// printf("%d%c", dp[i][j], j == 1 ? '\n' : ' ');
// }
// }
int ans = min({dp[N][-1], dp[N][0], dp[N][1]});
ans >= INF ? printf("BRAK\n")
: printf("%d\n", ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P3550 [POI2013]TAK-Taxis
题面
存在一条线段,有三个位置,$ 0
求最少需要多少辆出租车才能使 Byteasar 抵达目的地,无解输出
输入格式
Examples
Input_1
42 23 6 20 25 14 27 30 7
Output_1
4
Solution
本来看这题面感觉很像一道 DP,然后口糊了半天也没想到什么优秀的状态和转移。。。
所以这道题是个贪心。
大概就是显然每辆车是需要先先走到人所在的位置然后向着总部走,这个过程我们可以考虑到显然优先选路程上限更大的车是更优的,这个很显然吧,如果选路程小的那么去掉前往人所在位置的路程,最终的贡献就会更小,会导致后面的车无用的路程更多,贡献也会变小。
但如果只考虑这样可能会导致因为前面为了解更优而耗费了过多的大车导致最终只剩下路程小于
然后贪心是正确的,但我的之前证明假了。。
update:
考虑对于最后一段
如果最后一辆车不能送到终点,那么在有解的情况下一定至少都再需要一辆大于
下面的内容均为之前写的假的证明,思路错了,这里留作纪念。
这时候仔细想一下就会发现一个问题,如果我们在到达总部之前的,乘坐的最后一辆车的行驶距离足够长,可以把我们送到超过总部
我们可以考虑令按照贪心策略预留的车距离为
显然如果
反之则为一般情况:
若
若
同时注意,上述我们论述的均为到达总部前最后一辆车能将我们送到超过总部的位置,如果最后一辆车无法将我们送达总部,我们也不能直接判定为无解,需要考虑到预留的那辆车也可以先向总部左侧行驶一段后再向右行驶到目的地,所以在跳出循环之后还需要判断一下。
因此在程序中我们除了判定是否满足到达
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
/******************************
abbr
******************************/
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define int ll
template<typename T = int>
inline T read(void);
int M, D, N;
vector < int > dis;
signed main(){
// freopen("in.txt", "r", stdin);
M = read(), D = read(), N = read();
for(int i = 1; i <= N; ++i)dis.push_back(read());
sort(dis.begin(), dis.end(), greater < int >());
auto ptr = lower_bound(dis.rbegin(), dis.rend(), M - D);
if(ptr == dis.rend()){printf("0\n"); return 0;}
int val = *ptr;
dis.erase((++ptr).base());
int cur(0), ans(0);
for(auto i : dis){
cur += i - (D - cur);
++ans;
if(cur <= 0){--ans; break;}
if(cur >= M){printf("%lld\n", ans); return 0;}
if(cur >= D - (val - (M - D)) / 2){printf("%lld\n", ans + 1); return 0;}
// printf("cur:%d, ans:%d\n", cur, ans);
}
if((D - cur) * 2 + (M - D) <= val)printf("%lld\n", ans + 1);
else printf("0\n");
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P3560 [POI2013]LAN-Colorful Chain
题面
这道题的难度就难在理解洛谷的阴间翻译
给定长度为
每个
$ 1 \le n, m \le 10^6 int
范围内。
输入格式
Examples
Input_1
7 3 2 1 1 1 2 3 4 2 1 3 1 2 5
Output_1
2
Solution
很水的一道题。。。
显然符合要求的字串的长度一定为
写个滑动窗口,记录一下当前窗口内有多少符合要求的数,和 unordered_map
记录一下每个数在窗口内出现了几次,然后每次移动分类讨论一下符合要求的数是否变化,复杂度
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
/******************************
abbr
stms => sum_times
ctms => current_times
******************************/
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template<typename T = int>
inline T read(void);
int N, M;
unordered_map < int, int > seq;
int tms[1100000];
int ctms(0);
int stms(0);
int a[1100000];
int ans(0);
int main(){
N = read(), M = read();
int tmp[1100000];
for(int i = 1; i <= M; ++i)tmp[i] = read(), stms += tmp[i];
for(int i = 1; i <= M; ++i)tms[read()] = tmp[i];
for(int i = 1; i <= N; ++i)a[i] = read();
for(int i = 1; i <= stms; ++i){
if(seq.find(a[i]) != seq.end())seq[a[i]]++;
else seq.insert({a[i], 1});
if(seq[a[i]] <= tms[a[i]])++ctms;
}
if(ctms == stms)++ans;
for(int i = stms + 1; i <= N; ++i){
int tail = i - stms;
if(seq[a[tail]] <= tms[a[tail]])--ctms;
seq[a[tail]]--;
if(seq.find(a[i]) != seq.end()){
if(seq[a[i]] < tms[a[i]])++ctms;
seq[a[i]]++;
}else{
if(tms[a[i]])++ctms;
seq.insert({a[i], 1});
}
if(ctms == stms)++ans;
}
printf("%d\n", ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P3552 [POI2013]SPA-Walk
题面
存在
能到达输出 TAK
,反之输出 NIE
。
输入格式
(偷个懒就不写文字叙述了。。。)
Examples
Input_1
4 6 0000 1011 0110 0111 0011 1101 1010 1001
Output_1
TAK
Solution
这道题我本来还以为是和 LG-P7966 [COCI2021-2022#2] Hiperkocka 差不多的题,该题题解,按照那道题的思路想了一下然后假了,改了一下之后 T 掉了,寄,和之前的题关系不是很大,只是定义一样。
Lemma 1:对于一个
证明:A proof is left to the reader.
证明:这东西大多数地方都没有人证明,甚至官方题解,感觉可以当成一个已知结论记住,关于严谨证明网上也找到了个大佬的证明(看不懂),戳此进入。
Lemma 2:对于一个
证明:假设我们现在有两个连通块,其点集分别为
于是只要有了 Lemma 2,我们就可以很容易想到,令出发点和终点分别为
于是我们分别从 unordered_set
记录其所在连通块有哪些点,如果从搜索过程中遇到另一个点了,则两者同在一个小连通块中,直接输出连通然后 exit
,如果连通块大小超过 return
。两者都搜完后如果两个连通块都超过
另外个人建议把二进制转成 long long
再存,否则还会存在一个 hash 的复杂度,虽然我感觉好像嗯搞也能过???
然后写位移的时候记得写成 1ll
。
这个的复杂度大概是 6e8
的复杂度,比较离谱。
然后。。。Luogu 评测记录
unordered_set
寄了,cc_hash_table
寄的更多,经典被卡常,调了亿会怎么 Luogu 上都是两个点 TLE,一个点 MLE,于是开始手搓 hash 挂链,队列之类的,最终终于在 Luogu 上把这破题给卡过去了。。。
update:
然后 sssmzy 提出了一个很人类智慧的遍历方式,可以保证复杂度为
这个思路在 sssmzy 实现了之后发现假掉了,寄,仍然需要卡常。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
/******************************
abbr
uex => unexist
******************************/
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template<typename T = int>
inline T read(void);
ll str2ll(char* s, int n){
ll ret(0), base(0);
for(int i = n - 1; i >= 0; --i)
ret += (1ll << (base++)) * (s[i] == '0' ? 0 : 1);
return ret;
}
#define MOD 2737321
struct Edge{
Edge* nxt;
ll val;
OPNEW;
}eData[4145149];
ROPNEW(eData);
Edge* head[2737330];
void clear(void){memset(head, 0, sizeof(head));}
void Add(ll val){
int idx = val % MOD;
head[idx] = new Edge{head[idx], val};
}
bool Check(ll val){
int idx = val % MOD;
for(auto i = head[idx]; i; i = i->nxt)
if(i->val == val)return true;
return false;
}
ll S, T;
int N, K;
ll cur[5100000];
int fnt(0), til(0);
void bfs(ll S, ll T){
fnt = til = 0;
cur[til++] = S;
Add(S);
while(fnt < til){
ll tp = cur[fnt++];
for(int base = 0; base <= N - 1; ++base){
ll val = tp ^ (1ll << base);
if(val == T)printf("TAK\n"), exit(0);
if(Check(val))continue;
else{
Add(val);
cur[til++] = val;
}
}
if(til > N * K)return;
}
printf("NIE\n"); exit(0);
}
char c[65];
vector < ll > values;
int main(){
// freopen("in.txt", "r", stdin);
N = read(), K = read();
scanf("%s", c); S = str2ll(c, N);
scanf("%s", c); T = str2ll(c, N);
if(S == T)printf("TAK\n"), exit(0);
for(int i = 1; i <= K; ++i){
scanf("%s", c); ll tmpp = str2ll(c, N);
values.push_back(tmpp);
}
for(auto i : values)Add(i);
bfs(S, T);
clear();
for(auto i : values)Add(i);
bfs(T, S);
printf("TAK\n");
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P3556 [POI2013]MOR-Tales of seafaring
题面
给定一个
如果有输出 TAK
,反之输出 NIE
。
简单路径:每个点至多只能被访问一次的路径。
输入格式
Describe the graph. (m lines)
Examples
Input_1
8 7 4 1 2 2 3 3 4 5 6 6 7 7 8 8 5 2 3 1 1 4 1 5 5 8 1 8 10
Output_1
TAK NIE TAK NIE
Solution
显然因为不需要为简单路径,所以一个点可以访问多次,又因为点之间距离为
所以我们就可以发现只要
所以对于每次询问我们只需要跑一次从
发现 int
开不下,所以考虑把答案数组开成 short
即可。
然后我们发现这玩意似乎跑不了 Dijkstra,然后 SPFA 的话理论上是可以被卡到
考虑 bfs 方法,因为距离仅为
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
/******************************
abbr
******************************/
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template<typename T = int>
inline T read(void);
struct Edge{
Edge* nxt;
int to;
OPNEW;
}eData[21000];
ROPNEW(eData);
Edge* head[11000];
int N, M, K;
short dis[10100][10100];
bool vis[10100];
queue < int > q;
void bfs(int S){
memset(vis, false, sizeof(vis));
q.push(S);
// vis[S] = true;
while(!q.empty()){
int tp = q.front(); q.pop();
for(auto i = head[tp]; i; i = i->nxt){
if(vis[SON])continue;
vis[SON] = true;
dis[S][SON] = dis[S][tp] + 1;
q.push(SON);
}
}
}
int main(){
N = read(), M = read(), K = read();
for(int i = 1; i <= M; ++i){
int f = read(), t = read();
head[f] = new Edge{head[f], t + N};
head[t + N] = new Edge{head[t + N], f};
head[t] = new Edge{head[t], f + N};
head[f + N] = new Edge{head[f + N], t};
}
for(int i = 1; i <= N; ++i)bfs(i);
for(int i = 1; i <= K; ++i){
int f = read(), t = read(), d = read();
int diss = dis[f][(d & 1) ? t + N : t];
printf("%s\n", diss && d >= diss ? "TAK" : "NIE");
}
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P3563 [POI2013]POL-Polarization
题面
给定一棵有
定义本质不同连通点对为对于点
Examples
Input_1
4 1 2 1 3 1 4
Output_1
3 5
Solution
该说不说这道题的数据有点水,可以微调块长然后用一个不正确的贪心水过去。。。
首先对于第一个问题应该比较简单,如果说的专业一点就是,树是一个二部图,将其分解为左右部图后,把部图间的无向边全部改为左部图向右部图的有向边(反之亦然),则最小值一定为
或者通俗地说,把
对于第二个问题需要引入几个我不会证明很神奇的 Lemma:
Lemma 1:对于一个使连通点对数量最多的图,其中一定至少有一个点满足以其为根,所有子树要么是外向的要么是内向的,也就是说要么全部指向根方向,要么全部背向根方向。
Lemma 2:对于一个使连通点对数量最多的图,Lemma 1 的这个点一定在树的任意一个重心上。
证明:The proof is left to the reader. (不会证)
于是我们便可以发现,这道题的思路就是找到树的重心,然后以重心为根搜一下其每个子树的大小,记录下来之后枚举哪些子树是外向的,哪些是内向的,才会使最终答案最优。
如果是菊花图的话子树个数最多是
首先我们接着刚才的思路往下想此时我们根已经确定了,假设我们有这样一个图:
我们考虑除了根节点外的每一个节点,不难发现我们现在若仅考虑对每一个节点和该节点的所有子节点最高到达该节点的父亲的连通对,那么如果我们令节点
在这之后我们便不难发现只剩下如
于是显然有
我们将所有子树大小抽象成一个序列,那么我们的目标就是要将这个序列分成两部分,使两部分分别求和后乘积最大。两个数和固定,要让积最大,这玩意应该很显然就是要让两个数相等吧,放到这道题上就是让两部分的求和后的差值最小,这东西不觉得非常像搭建双塔吗。。。关于一个人类智慧的DP - Vijos 1037 搭建双塔
不过我们仔细观察一下后发现实际上并不一样,本题里我们需要将所有的数都用上,且搭建双塔的时间复杂度放在这题上就很离谱了。
首先这里提供一个奇怪的贪心,据说是 2015 集训队论文里的(虽然我没找到)(而且是错误的),大概就是维护一个大根堆,然后每次取堆顶的两个值,计算它们差的绝对值然后再插入堆里,当堆中剩余一个元素的时候这个元素就是差值的最小值,看起来似乎很奇怪,细想一下似乎很正确,但是这是错误的(如果不是我想错了的话)。
这个贪心大概的思路就是每次找最大的两个分别放在两侧,会有一个差值,然后我们可以将这个差值也认为是一个新的数,显然差值也可以和普通的数一样放置,比如说两对差值为
但是这个东西是可以过的,可以发现如果进行根号分治,但是不按照
Code:
const int B = 1000;//int(sqrt(N));
if(tot >= B){
std::priority_queue < int, vector < int >, less < int > > vals;
for(auto i : subt)vals.push(i);
while(vals.size() != 1){
int x = vals.top(); vals.pop();
int y = vals.top(); vals.pop();
vals.push(abs(x - y));
}
int diff = vals.top();
int vx = (N - 1 - diff) / 2;
int vy = vx + diff;
ans += (ll)vx * vy;
}else{
dp[0] = true;
for(auto i : subt)dp |= dp << i;
for(int i = N / 2 + 1; i >= 0 ; --i)if(dp[i]){ans += (ll)i * (N - i - 1); break;}
}
考虑正解,开一个大小为 bool
类型数组,表示
然后发现复杂度依然不正确,考虑把这个数组压成一个 bitset
,这样在进行或运算的时候还可以大幅降低速度,最终是
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template<typename T = int>
inline T read(void);
struct Edge{
Edge* nxt;
int to;
OPNEW;
}ed[510000];
ROPNEW(ed);
Edge* head[260000];
int N;
int siz[260000], msiz[260000], rt(0);
void dfs(int p, int fa = -1){
msiz[p] = 0;
siz[p] = 1;
for(auto i = head[p]; i; i = i->nxt){
if(SON == fa)continue;
dfs(SON, p);
siz[p] += siz[SON];
msiz[p] = max(msiz[p], siz[SON]);
}
msiz[p] = max(msiz[p], N - siz[p]);
if(!rt || msiz[p] < msiz[rt])rt = p;
}
int cnt[260000];
int tot(0);
ll ans(0);
bitset < 260000 > dp;
int main(){
N = read();
for(int i = 1; i <= N - 1; ++i){
int s = read(), t = read();
head[s] = new Edge{head[s], t};
head[t] = new Edge{head[t], s};
}
dfs(1);
dfs(rt);
for(int i = 1; i <= N; ++i)if(i != rt)ans += siz[i];
for(auto i = head[rt]; i; i = i->nxt)cnt[siz[SON]]++;
for(int i = 1; i <= N / 2; ++i)while(cnt[i] > 2)cnt[i] -= 2, cnt[i * 2]++;
dp[0] = true;
for(int i = 1; i <= N; ++i)while(cnt[i]--)dp |= dp << i;
for(int i = N / 2; i >= 0 ; --i)if(dp[i]){ans += (ll)i * (N - i - 1); break;}
printf("%d %lld\n", N - 1, ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P3551 [POI2013]USU-Take-out
题面
给定
每次消除
其中
输入格式
Describe the brick sequence.
Examples
Input_1
12 2 ccbcbbbbbbcb
Output_1
1 8 12 2 6 7 3 4 5 9 10 11
Solution
正难则反(怎么又是正难则反)。
我估计你们都能简单想出来个大致的思路,但是吧这玩意需要个关键的性质:
对于一个有解的序列,只要其满足白色块是黑色的
但是这个性质并没有找到任何的证明。。。只能感性理解一下这个似乎是正确的。。。吧?
所以我们逆向去思考,我们最后删除的一定是一段连续的
考虑维护个栈,并且维护一个类似滑动窗口的东西,也就是维护栈中最后
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
/******************************
abbr
******************************/
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template<typename T = int>
inline T read(void);
bool readc(void){
char c = getchar();
while(c != 'c' && c != 'b')c = getchar();
return c == 'c' ? true : false;
}
int N, K;
stack < int > seq;
int sum[1010000];
int cnt(0);
std::priority_queue < int, vector < int >, greater < int > > ans[1010000];
int main(){
N = read(), K = read();
for(int i = 1; i <= N; ++i){
bool f = readc();
seq.push(i);
sum[seq.size()] = sum[seq.size() - 1] + f;
if((int)seq.size() >= K + 1 && sum[seq.size()] - sum[seq.size() - K - 1] == 1){
++cnt;
for(int j = 1; j <= K + 1; ++j)ans[cnt].push(seq.top()), seq.pop();
}
}
for(int i = cnt; i >= 1; --i){
while(!ans[i].empty()){
int v = ans[i].top(); ans[i].pop();
printf("%d ", v);
}printf("\n");
}
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P3553 [POI2013]INS-Inspector
题面
给你一个公司,公司里有
多组数据,数据组数为
输入格式
Examples
Input_1
2 3 5 1 1 1 1 2 1 2 3 1 4 1 1 4 2 1 3 3 3 3 0 2 2 0 1 1 0
Output_1
4 3
Solution
题目本身并没有什么高深的算法或者奇怪的思路,嗯做即可。
二分答案应该很显然吧???这样可以把求解改为验证正确性。
不难想到我们可以把每个人抽象成一个线段,那么每个人都有一个必须要覆盖的区间,也就是会有一段时间段里某个人必须一直工作,并且他们覆盖的区间在合法的情况下是可以向左右无限延伸的,而对于没有任何的限制的人我们可以认为他们是自由的,可以随意延申,区间没有被确定。
我们先将这个处理出来,然后整体跑一遍,过程中可以考虑维护以下几个状态:
然后还需要注意特判一下一些无解的情况,比如有两个人的描述冲突,自由的人变为负数,所有能用的人加一起都无法满足必须要选择的人数等,跑一圈即可,注意每次判断时的初始化。
时间复杂度大概是
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
/******************************
abbr
tm/tim => time
lt/lft => left
bg => begin
ed => end
******************************/
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define MAXNM 110000
#define CLEAR(arr) memset(arr, 0, sizeof(arr))
template<typename T = int>
inline T read(void);
int N, M;
tuple < int, int, int > in[MAXNM];
struct Worker{int l, r;}wk[MAXNM];
int bg[MAXNM], ed[MAXNM];
int note[MAXNM];
void clear(void){
CLEAR(wk);
CLEAR(note);
CLEAR(bg), CLEAR(ed);
}
bool check(int lim){
clear();
for(int i = 1; i <= lim; ++i){
int tm, n, lt;
tie(tm, n, lt) = in[i];
if(note[tm] && note[tm] != lt)return false;
note[tm] = lt,
wk[n].l = min(wk[n].l ? wk[n].l : INT_MAX, tm),
wk[n].r = max(wk[n].r, tm);
}
for(int i = 1; i <= N; ++i)if(wk[i].r)++bg[wk[i].l], ++ed[wk[i].r];
int cur(0), fre(N), extl(0), extr(0);
for(int i = 1; i <= M; ++i){
if(!note[i])continue;
cur += bg[i];
if(cur > note[i])return false;
while(bg[i]--)extl ? --extl : --fre;
while(cur + extl + extr < note[i])--fre, ++extl;
if(fre < 0)return false;
while(cur + extl + extr > note[i])extr ? --extr : --extl;
extr += ed[i], cur -= ed[i];
}
return true;
}
int main(){
int T = read();
while(T--){
// in.clear(), in.shrink_to_fit();
N = read(), M = read();
for(int i = 1; i <= M; ++i){
int tm = read(), num = read(), lft = read(); ++lft;
in[i] = {tm, num, lft};
}
int l = 1, r = M;
int ans(-1);
while(l <= r){
int mid = (l + r) >> 1;
check(mid) ? l = mid + 1, ans = mid : r = mid - 1;
}
printf("%d\n", ans);
}
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P3557 [POI2013]GRA-Tower Defense Game
题面
给定一张
输入格式
Describe the graph.
Examples
Input_1
9 8 3 1 2 2 3 3 4 1 4 3 5 4 6 7 8 8 9
Output_1
3 1 5 7
Solution
这题真的是近期一段时间内做过最舒服的题了。
什么普及减。
看起来很高端的一道构造题,我第一眼看到这道题也有点迷,用
我还以为是翻译的问题,可能是 SPJ 最多
于是我们简单动一丢丢脑子想一下就会发现,既然摆
乍一看这个似乎很假,感觉一定不是很优,但是我们细想一下,这个东西实际上是可以证明的。
既然我们摆放
时间复杂度:这个应该算是
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template<typename T = int>
inline T read(void);
struct Edge{
Edge* nxt;
int to;
OPNEW;
}ed[2100000];
ROPNEW(ed);
Edge* head[510000];
int N, M, K;
bool atk[510000];
int cnt(0);
void Attack(int p, int dis = 0){
atk[p] = true;
if(dis >= 2)return;
for(auto i = head[p]; i; i = i->nxt)Attack(SON, dis + 1);
}
int main(){
N = read(), M = read(), K = read();
for(int i = 1; i <= M; ++i){
int s = read(), t = read();
head[s] = new Edge{head[s], t};
head[t] = new Edge{head[t], s};
}
vector < int > ans;
for(int i = 1; i <= N; ++i)if(!atk[i])ans.push_back(i), Attack(i);
printf("%d\n", (int)ans.size());
for(auto i : ans)printf("%d ", i);
printf("\n");
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P3554 [POI2013]LUK-Triumphal arch
题面
给定一棵
Examples
Input_1
7 1 2 1 3 2 5 2 6 7 2 4 1
Output_1
3
Solution
这道题拿到手之后就口糊了一个感觉还挺正确的贪心算法,后来看了一下跟正解还是比较相似的,二分答案应该很显然,首先 dfs 预处以 tuple < int, int, int >
的 std::priority_queue
,手写一个比较函数,处理一下每个点剩余的没有被染色的子节点数和深度,优先染深度较浅的,深度相同的情况下优先染子节点数更多的点的子节点,然后再把它丢回去找子节点数最多的继续去染他的子节点,直到多出来的染色次数用完。
没怎么分析复杂度,加了点剪枝,感觉复杂度会很奇怪,最终也验证了我的猜想,正确性似乎没什么问题,但是时间上直接爆炸,
大概的思路也还是先 dfs 以下求子节点数(这一步可以省略,仅为了剪枝而存在)。
然后依然是二分答案,显然这步是可以剪枝的,下界就是根节点的子节点数,上界就是所有点中最大的子节点数。
思考这题的一个很显然的思路,如果我们目光短浅一点只考虑当前点的话,很显然一个正确的贪心策略就是优先把当前所在点的所有子节点都染成黑色,否则 B 就直接获胜了。然后剩余染色次数才可以给子节点,于是我们可以考虑记录每个节点需要多少“帮助”才能达到要求,令其为
复杂度是
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template<typename T = int>
inline T read(void);
struct Edge{
Edge* nxt;
int to;
OPNEW;
}ed[610000];
ROPNEW(ed);
Edge* head[310000];
int N;
int cson[310000];
int dp[310000];
void dfs(int p = 1, int fa = 0){
for(auto i = head[p]; i; i = i->nxt)if(SON != fa)++cson[p], dfs(SON, p);
}
void MakeDP(int k, int p = 1, int fa = 0){
dp[p] = cson[p] - k;
for(auto i = head[p]; i; i = i->nxt)
if(SON != fa){
MakeDP(k, SON, p);
dp[p] += max(0, dp[SON]);
}
}
bool check(int k){
MakeDP(k);
return dp[1] <= 0;
}
int main(){
N = read();
for(int i = 1; i <= N - 1; ++i){
int s = read(), t = read();
head[s] = new Edge{head[s], t};
head[t] = new Edge{head[t], s};
}
dfs();
int l = cson[1], r = N - 1, ans = -1;
while(l <= r){
int mid = (l + r) >> 1;
check(mid) ? ans = mid, r = mid - 1 : l = mid + 1;
}printf("%d\n", ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P3547 [POI2013]CEN-Price List
题面
给定一个边权均为
输入格式
Describe the graph.
Examples
Input_1
5 5 1 3 2 1 2 2 3 3 4 4 5 3 1
Output_1
0 3 3 2 5
Solution
这题我感觉不太像黑的啊,紫色比较合适吧。。。
显然我们考虑对于任意两个点
- 走了
次 路径。 - 走了
次 路径和 次 路径。 - 走了
次 路径。
显然前两种都可以通过宽搜
关于复杂度的证明和三元环计数差不多,大概就是从度数和
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template<typename T = int>
inline T read(void);
vector < int > vert[110000], dvert[110000];
int N, M;
int K, a, b;
int dis[110000];
bool vis[110000];
int tag[110000];
int ans[110000];
int main(){
N = read(), M = read(), K = read(), a = read(), b = read();
for(int i = 1; i <= M; ++i){
int s = read(), t = read();
vert[s].push_back(t), vert[t].push_back(s);
dvert[s].push_back(t), dvert[t].push_back(s);
}
queue < int > vrt;
vrt.push(K); vis[K] = true;
while(!vrt.empty()){
int p = vrt.front(); vrt.pop();
for(auto i : vert[p])
if(!vis[i])dis[i] = dis[p] + 1, vis[i] = true, vrt.push(i);
}
for(int i = 1; i <= N; ++i)ans[i] = min(dis[i] * a, (dis[i] / 2 * b + (dis[i] & 1) * a));
memset(dis, -1, sizeof(dis));
vrt.push(K); dis[K] = 0;
while(!vrt.empty()){
int p = vrt.front(); vrt.pop();
for(auto i : vert[p])tag[i] = p;
for(auto i : vert[p]){
auto it = dvert[i].begin();
while(it != dvert[i].end()){
if(tag[*it] == p || ~dis[*it])++it;
else vrt.push(*it), dis[*it] = dis[p] + 1, it = dvert[i].erase(it);
}
}
}
for(int i = 1; i <= N; ++i)if(~dis[i])ans[i] = min(ans[i], dis[i] * b);
for(int i = 1; i <= N; ++i)printf("%d\n", ans[i]);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P3562 [POI2013] LAS-Laser
题面
给定平面上的
输入格式
(n lines in total)
Examples
Input_1
3 6 1 2 2 4 3 1 5 1 3 2 2 3 3 3 3 4 2 2 2 2 6 1 3 5
Output_1
5
Solution
首先本题有一个比较显而易见的贪心,即最优的情况下每条射线至少穿过一条线段的端点,感性理解一下,如果不穿过端点那么移动到端点一定是不劣的。
我们可以考虑一个类似极角排序的思想,把每一个点当成一个向量的坐标表示,按照角度进行排序离散化并尝试寻找性质:
可以看下图:
排序离散化后,每个点旁边的数字即为其离散化后的坐标,于是我们发现,每一条线段都变成了一个区间:
同时注意,离散化后的每一个点都是至少一个区间的端点!
而我们从原点引出的每一条射线,也可以抽象成一个向量的坐标,或者换句话说,是这些区间中的一个点。
于是此时我们便可以发现问题变成了在给定区间中选择一些点,使这些点涉及的区间尽可能多,且每个区间中最多只有一个点。
对于这个问题应该是一个显然的 DP,我们考虑设
考虑转移,显然可以通过
显然选择第
于是我们的方程便为
通过差分预处理一下
然后注意观察到
时间复杂度是
Tips:对于具体的离散化实现,可以通过向量的外积,大于零则前者在后者的顺时针方向,反之亦然,可以考虑写个结构体重载小于号和等于号,或者在 sort
和 unique
和 lower_bound
的时候写个 lambda 函数也行,亦或自己纯手搓一个离散化也可以实现,具体可以看代码。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
/******************************
abbr
segl => line segment
lft => left
******************************/
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template<typename T = int>
inline T read(void);
struct Point{
int x, y;
friend ll operator * (const Point &a, const Point &b){
return (ll)a.x * b.y - (ll)a.y * b.x;
}
friend bool operator < (const Point &a, const Point &b){
return a * b < 0;
}
friend bool operator == (const Point &a, const Point &b){
return a * b == 0;
}
};
int K, N;
pair < Point, Point > segl[510000];
pair < int, int > seg[510000];
vector < Point > arr;
int cnt[1100000];
int lft[1100000];
int dp[1100000];
int main(){
K = read(), N = read();
for(int i = 1; i <= N; ++i){
int x1 = read(), y1 = read(), x2 = read(), y2 = read();
segl[i] = {Point{x1, y1}, Point{x2, y2}};
arr.push_back(Point{x1, y1}), arr.push_back(Point{x2, y2});
}
sort(arr.begin(), arr.end(), [](const Point &a, const Point &b)->bool{return a * b < 0;});
auto endpos = unique(arr.begin(), arr.end());
int siz = distance(arr.begin(), endpos);
for(int i = 1; i <= N; ++i){
seg[i].first = distance(arr.begin(), lower_bound(arr.begin(), endpos, segl[i].first) + 1);
seg[i].second = distance(arr.begin(), lower_bound(arr.begin(), endpos, segl[i].second) + 1);
if(seg[i].first > seg[i].second)swap(seg[i].first, seg[i].second);
}
for(int i = 1; i <= siz; ++i)lft[i] = siz;
for(int i = 1; i <= N; ++i){
++cnt[seg[i].first], --cnt[seg[i].second + 1];
lft[seg[i].second] = min(lft[seg[i].second], seg[i].first);
}
for(int i = 1; i <= siz; ++i)cnt[i] += cnt[i - 1];
for(int i = siz - 1; i >= 1; --i)lft[i] = min(lft[i], lft[i + 1]);
for(int j = 1; j <= K; ++j){
for(int i = siz; i >= 1; --i)dp[i] = max(dp[i], dp[lft[i] - 1] + cnt[i]);
for(int i = 1; i <= siz; ++i)dp[i] = max(dp[i], dp[i - 1]);
}
printf("%d\n", dp[siz]);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P3548 [POI2013] GOB-Tapestries
题面 & Solution
一道 Luogu 上通过为
本来准备 skip,然后在某位学长的 Blog 里找到了个题解,于是想尝试一下,写了一半,然后知道了计算几何在 NOI 系列比赛里基本不考,然后。。。
咕咕咕。
关于某个唯一找到的不是波兰文的题解 戳此进入。
Code(incomplete)
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
/******************************
abbr
******************************/
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template<typename T = int>
inline T read(void);
bool readflag(void);
struct Point{
ld x, y;
friend Point operator + (const Point &a, const Point &b){
return Point{a.x + b.x, a.y + b.y};
}
friend Point operator - (const Point &a, const Point &b){
return Point{a.x - b.x, a.y - b.y};
}
friend Point operator * (const ld &k, const Point &a){
return Point{k * a.x, k * a.y};
}
};
struct Vec{
ld x, y;
friend ld operator * (const Vec &a, const Vec &b){
return (ld)a.x * b.y - (ld)a.y * b.x;
}
friend bool operator < (const Vec &a, const Vec &b){
return a * b < 0;//TODO eps
}
friend bool operator > (const Vec &a, const Vec &b){
return a * b > 0;
}
friend bool operator == (const Vec &a, const Vec &b){
return a * b == 0;
}
};
Vec MakeVec(Point a, Point b){return Vec{b.x - a.x, b.y - a.y};}
Vec Point2Vec(Point p){return Vec{p.x, p.y};}
Point Vec2Point(Vec v){return Point{v.x, v.y};}
struct Line{
Point P;
Vec V;
};
Line MakeLine(Point a, Point b){return Line{Point{a.x, a.y}, MakeVec(a, b)};}
struct Segl{
Point a, b;
};
int N;
Point p[1100];
int JudgePointPos(Point a, Line l){// 1 - left; -1 - right; 0 - on
auto ret = l.V * MakeVec(l.P, a);
return ret > 0 ? 1 : (ret < 0 ? -1 : 0);
}
int JudgeSeglPos(Segl sl, Line l){// 1 - left; -1 - right; 0 - cross
int a = JudgePointPos(sl.a, l), b = JudgePointPos(sl.b, l);
return (a == 1 && b == -1) || (a == -1 && b == 1)
? 0
: (a == 1 || b == 1)
? 1
: -1;
}
Point GetInter(Line l1, Line l2){//Intersection
ld k = (ld)(Point2Vec(l2.P - l1.P) * l2.V) / (ld)(l1.V * l2.V);
return l1.P + k * Vec2Point(l1.V);
}
bool check(void){
}
int main(){
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
bool readflag(void){
char c = getchar();
while(c != 'C' && c != 'S')c = getchar();
return c == 'S' ? true : false;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
LG-P3559 [POI2013] LAB-Maze
题面 & Solution
一道似乎比上一题更离谱的计算几何,依然完全没有任何题解,只有波兰文的。
咕咕咕。
LG-P3549 [POI2013]MUL-Multidrink
题面
一道论文题。
给定一棵有
如果无解输出 BARK
,否则输出
输入格式
Describe the tree(
lines in total).
Examples
Input_1
12 1 7 7 8 7 11 7 2 2 4 4 10 2 5 5 9 2 6 3 6 3 12
Output_1
1 11 8 7 4 10 2 9 5 6 3 12
图例
Solution
本题大多数内容均参考自论文《Hamiltonian paths in the square of a tree》。
作者:Jakub Radoszewski and Wojciech Rytter。
原文链接:Hamiltonian paths in the square of a tree
首先我们引入一个定义:caterpillar(毛毛虫),原文定义为 a single path with several leafs connected to it. 简而言之就是其为一棵树,一棵树为 caterpillar 当且仅当去掉其所有叶子节点后剩下的仅为一条单链,如图即为一个 caterpillar。
对于其更多的性质及求法,可以去做一下 LG-P3174 [HAOI2009] 毛毛虫,但与本题关联不大,就不再赘述。
现在我们引入下一个定义,对于图
然后我们再引入一个定义:2-connected graph(2-连通图),这个定义类似点双,对于具有割点的图我们定义其为 1-connected graph,则同理 2-connected graph 只得就是至少需要去掉两个顶点才能使图的连通分支增加。
现在我们再引入几个定理:
Lemma 1:对于树
Lemma 2:对于树
证明:咕咕咕。
然后我们继续进行定义。
定义
额外地,我们分别定义
若
然后对于 caterpillar,我们额外定义其为 non-trivial 当且仅当其至少含有
我们称 caterpillar 的主链,也就是去掉叶子结点剩下的链,为 spine。
下面开始正文:
我们考虑将原图中 当然严谨的证明我不会。
然后这里我们对于每个主干上的点划分为两类:
- Type A:有不超过
个 non-trivial caterpillar。 - Type B:有且仅有
个 non-trivial caterpillar。
不难发现,对于 Type A 的节点,若从主干上的节点进入,最后一步一定刚好走到下一个主干节点上,对于 Type B 的节点,在进入时必须直接进入到其任意子树中,否则无解,对于后面的无解判断我们主要也就靠这个性质。
这里有一个性质:若一个点有超过两个的 non-trivial caterpillar 则一定无解,这个依然,自己画一下会发现无论上一步刚好走到这个点还是直接走到某一个子树上,最终都是无解的,路径一定会断在某个子树中,同样严谨的证明我不会。
然后我们额外地定义一个点为 free 当且仅当其为单点,即没有任何子树的主干节点。
如下图例:
下一个性质:每个 Type B 的节点前后都必须有至少一个 free 节点,否则无解。显然 free 点是可以将进入下一个主干节点和进入下一个主干节点的某一个子节点相互转换的,而 Type A 不能进行这样的转换,两棵 Type B 之间又需要有这样的转换。
对于满足以上所有性质的主干,我们称其为
现在我们对上面的性质做一个总结:
- 每一个主干节点都为 Type A 或 Type B。
- 在任意两个 Type B 之间都至少有一个 free。
- 在 Type B 之前至少有一个 free。
- 在 Type B 之后至少有一个 free。
- 至少有一个 free。
显然对于上面的四个图例中,(a)(b) 为 horsetail,有解,(c)(d) 非 horsetail,无解。
而对于我们本题的图
现在我们对 Lemma 1 进行进一步的阐释:
Lemma 1:任意一个在 caterpillar 上的 2H-cycle 都一定有以下的顺序:我们定义 caterpillar 的 spine 长度为
如此图中,显然
所以当我们想要对 horsetail 上的 caterpillar 找到 2H-path 的时候,只需要取 2H-cycle 中的一部分,或者说断开 2H-cycle 中的一条边,如
然后再次回到本题,或者说回到 Lemma 2:
对于一个 2H-path
对于我们的算法我们首先需要实现一些辅助函数:(为了便于理解,部分函数名与原论文中有微调)
vector < int > BuildCaterpillar(int mp, int S, int T)
:
对于从 void BuildSpine(int fa, int p)
实现,我们称 caterpillar 所在的 horsetail 上的主干节点为 void extend(int mp, int unreach1 = -1, int unreach2 = -1)
实现,具体可以看一下代码。
int FindAnySecondaryNode(int p)
:找到 horsetail 中主干为
int FindAnySecondaryNode_PreferablyLeaf(int p)
:找到 horsetail 中主干为
int FindAnotherSecondaryNode(int p, int lst)
:找到 horsetail 中主干为
然后对于具体的实现,首先我们需要检查一下其是否为无解,也就是前面的检查是否为 horsetail,具体由 void Check(void)
实现。
然后我们算法的核心就是找到 horsetail 的那一条 2H-path,通过 vector < int > Get2HPathHorsetail(void)
实现,大概的思路就是先对第一个节点进行讨论,显然起点一定为
大致的思路就是这样,码量大概 7.5KiB
,随后的复杂度是 linear 的,也就是
同时这里提供一个原论文中的对于最后的 Get2HPathHorsetail(void)
的实现,可以选择性地看一下。
Code
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
/******************************
abbr
mp => mainp
subt => subtree
fa => father
fst => first
lst => last
******************************/
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define EXIT puts("BRAK"), exit(0)
#define MAXN 510000
template<typename T = int>
inline T read(void);
struct Edge{
Edge* nxt;
int to;
OPNEW;
}ed[(MAXN << 1) + MAXN];
ROPNEW(ed);
Edge* head[MAXN];
int N;
int mainLen(0);
int fa[MAXN], deg[MAXN];
int mainp[MAXN];
int non_trivial[MAXN];
bool isMainp[MAXN], isFree[MAXN];
void dfs(int p = 1){
for(auto i = head[p]; i; i = i->nxt)
if(SON != fa[p])
fa[SON] = p,
dfs(SON);
}
void InitMainp(void){
int cur = N;
do{
mainp[++mainLen] = cur;
isMainp[cur] = true;
cur = fa[cur];
}while(cur != 1);
mainp[++mainLen] = 1;
isMainp[1] = true;
reverse(mainp + 1, mainp + mainLen + 1);
}
bool isCaterpillar(int fa, int p){
int cnt(0);
for(auto i = head[p]; i; i = i->nxt){
if(SON == fa || deg[SON] == 1)continue;
if(!isCaterpillar(p, SON))return false;
if(cnt++)return false;
}return true;
}
void Check(void){
for(int p = 1; p <= mainLen; ++p){
int mp = mainp[p];
isFree[mp] = true;
for(auto i = head[mp]; i; i = i->nxt){
if(isMainp[SON])continue;
isFree[mp] = false;
if(deg[SON] == 1)continue;
++non_trivial[mp];
if(non_trivial[mp] > 2)EXIT;
if(!isCaterpillar(mp, SON))EXIT;
}
}
int curFree(0);
bool end_with_B(true);
bool exist_free(false);
for(int p = 1; p <= mainLen; ++p){
int mp = mainp[p];
if(isFree[mp]){++curFree; exist_free = true; end_with_B = false; continue;}
if(non_trivial[mp] == 2){
if(!curFree)EXIT;
curFree = 0;
end_with_B = true;
}
}
if(end_with_B || !exist_free)EXIT;
}
int FindAnySecondaryNode(int p){
for(auto i = head[p]; i; i = i->nxt)
if(!isMainp[SON])return SON;
return -1;
}
int FindAnySecondaryNode_PreferablyLeaf(int p){
for(auto i = head[p]; i; i = i->nxt)
if(!isMainp[SON] && deg[SON] == 1)return SON;
return FindAnySecondaryNode(p);
}
int FindAnotherSecondaryNode(int p, int lst){
for(auto i = head[p]; i; i = i->nxt)
if(!isMainp[SON] && SON != lst)return SON;
return -1;
}
namespace Caterpillar{
vector < int > route;
Edge* head[MAXN];
vector < int > spine;
enum type{spineNode = 1, leafNode};
int ffa[MAXN];
void add(int s, int t){
head[s] = new Edge{head[s], t};
ffa[t] = s;
}
void BuildSpine(int fa, int p){
spine.push_back(p);
for(auto i = ::head[p]; i; i = i->nxt){
if(SON == fa)continue;
if(::deg[SON] == 1)add(p, SON);
else BuildSpine(p, SON);
}
}
void extend(int mp, int unreach1 = -1, int unreach2 = -1){
for(auto i = head[mp]; i; i = i->nxt){
if(SON == unreach1 || SON == unreach2)continue;
route.push_back(SON);
}
}
vector < int > BuildCaterpillar(int mp, int S, int T){
route.clear();
spine.clear();
route.push_back(S);
if(S == T)return route;
spine.push_back(mp);
bool exist_caterpillar(false);
for(auto i = ::head[mp]; i; i = i->nxt){
if(isMainp[SON])continue;
if(deg[SON] == 1)add(mp, SON);
else{
if(!exist_caterpillar)exist_caterpillar = true;
else reverse(spine.begin(), spine.end());
BuildSpine(mp, SON);
}
}
vector < pair < int, type >/*spine_node_pos, spine or leaf*/ > temp;
vector < pair < int, type >/*spine_node_pos, spine or leaf*/ > unextended;
for(int i = 0; i < (int)spine.size(); ++i)
temp.push_back({spine.at(i), !(i & 1) ? spineNode : leafNode});
for(int i = (int)spine.size() - 1; i >= 0; --i)
temp.push_back({spine.at(i), (i & 1) ? spineNode : leafNode});
for(auto it = temp.begin(); it < temp.end(); ++it)
if(it->second == spineNode || head[it->first])unextended.push_back(*it);
#define LEFT(x) (x == 0 ? (int)unextended.size() - 1 : x - 1)
#define RIGHT(x) (x == (int)unextended.size() - 1 ? 0 : x + 1)
auto Beg = deg[S] == 1 ? make_pair(ffa[S], leafNode) : make_pair(S, spineNode);
auto End = deg[T] == 1 ? make_pair(ffa[T], leafNode) : make_pair(T, spineNode);
int begPos = -1; while(unextended.at(++begPos) != Beg);
if(Beg.second == leafNode)extend(Beg.first, S, T);
if(unextended.at(LEFT(begPos)) == End)
for(int j = RIGHT(begPos); unextended.at(j) != End; j = RIGHT(j))
unextended.at(j).second == spineNode
? route.push_back(unextended.at(j).first)
: extend(unextended.at(j).first);
else
for(int j = LEFT(begPos); unextended.at(j) != End; j = LEFT(j))
unextended.at(j).second == spineNode
? route.push_back(unextended.at(j).first)
: extend(unextended.at(j).first);
if(End.second == leafNode && Beg != End)extend(End.first, S, T);
route.push_back(T);
return route;
}
}
vector < int > Get2HPathHorsetail(void){
vector < int > ret;
int fst = mainp[1];
int lst = isFree[mainp[1]]
? mainp[1]
: FindAnySecondaryNode(mainp[1]);
auto tmp = Caterpillar::BuildCaterpillar(mainp[1], fst, lst);
ret.insert(ret.end(), tmp.begin(), tmp.end());
for(int i = 2; i <= mainLen; ++i){
int w = mainp[i];
if(isFree[w])fst = lst = w;
else if(!isMainp[lst])
fst = w,
lst = FindAnySecondaryNode_PreferablyLeaf(w);
else
fst = FindAnySecondaryNode_PreferablyLeaf(w),
lst = non_trivial[w] == 2
? FindAnotherSecondaryNode(w, fst)
: w;
auto cp = Caterpillar::BuildCaterpillar(w, fst, lst);
ret.insert(ret.end(), cp.begin(), cp.end());
}
return ret;
}
int main(){
N = read();
for(int i = 1; i <= N - 1; ++i){
int s = read(), t = read();
head[s] = new Edge{head[s], t};
head[t] = new Edge{head[t], s};
++deg[s], ++deg[t];
}
dfs();
InitMainp();
Check();
auto ans = Get2HPathHorsetail();
for(auto i : ans)printf("%d\n", i);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
写在后面
就是这道不怎么友好的题,我做的时候网上几乎没有题解,只能找到几篇说的很简略的题解,然后没办法只能去自己翻英文论文,然后一个一个查词,在我快要写完这道题的时候,才突然发现 Luogu 上多出来了两篇题解。。
然后就是调这道题,一共交了
UPD
update-2022_09_26 完成 Bytecomputer Taxis Colorful Chain
update-2022_09_27 完成 Walk Tales-of-seafaring
update-2022_09_28 完成 Polarization Take-out
update-2022_09_29 完成 Inspector Tower Defense Game
update-2022_10_03 完成 Triumphal arch Price List
update-2022_10_04 完成 Laser
update-2022_10_04 跳过 Tapestries Maze
update-2022_10_08 修改了很多小错误
update-2022_10_09 完成 Multidrink
update-2022_10_10 修复 Multidrink 的错误
本文作者:Tsawke
本文链接:https://www.cnblogs.com/tsawke/p/16786813.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步