P2403 [SDOI2010]所驼门王的宝藏
Posted Jozky86
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P2403 [SDOI2010]所驼门王的宝藏相关的知识,希望对你有一定的参考价值。
题意:
R * C的地图上有n个宝藏,给你n个宝藏的坐标,每个宝藏的位置上还有一个传送门,传送门有三种类型,1.可以传送到同行的其他宝藏位置,2.可以传送到同列的其他宝藏位置 3.可以传送到该点周围的八个位置
你可以在任意一个宝藏处开始,问最多获得多少宝藏?
题解:
如果我们直接按照题意要求建边,(暴力建边,第1类门和每行其他门建边,第2,3类门同理),会得到一个有向有环图,对于环我们可以用tarjan进行缩点,然后得到DAG,然后就是在DAG直接dp求就行(拓扑排序)。
d
p
[
v
]
=
m
a
x
(
d
p
[
u
]
+
v
a
l
[
v
]
)
dp[v]=max(dp[u]+val[v] )
dp[v]=max(dp[u]+val[v])
但是数据告诉我们不会这么简单,N<=100000,R<=1000000,C<=1000000
边的数量巨大,可达O(
n
2
n^2
n2),所以需要一个巧妙的建边方法来降低复杂度
原始的建边是两两之间建立练习,避免不了是O(
n
2
n^2
n2),现在我们可以这样想,对于每行,每列都建立一个额外的点,然后让所有点与这个额外的点建立联系。
第i行的额外的点为x,我们让x向改行所有宝藏建一个边,如果有一个点y是1号门,我们就让y再向x建一个边,通过这样操作,y就是本行任意一个宝藏位置(很妙,这个思路很妙)
列的同理
对于第三种门,我们就暴力枚举八个位置,然后在所有宝藏中二分寻找(事先要对宝藏位置排序),看是否能找到,找到即建边
思路捋清楚,就开干码吧
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll read(){
ll s=0,w=1ll;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1ll;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10ll+((ch-'0')*1ll),ch=getchar();//s=(s<<3)+(s<<1)+(ch^48);
return s*w;
}
clock_t startTime, endTime;
void rd_test(){
#ifdef ONLINE_JUDGE
#else
startTime = clock(); //计时开始
freopen("sotomon.in","r",stdin);
#endif
}
void Time_test(){
#ifdef ONLINE_JUDGE
#else
endTime = clock(); //计时结束
printf("\\n运行时间为:%lfs\\n",(double)(endTime - startTime) / CLOCKS_PER_SEC);
#endif
}
const int N = 2e5+9, T = 2100007, M = 1000007;
const int dx[8] = {1, 1, 1, 0, 0, -1, -1, -1};
const int dy[8] = {1, 0, -1, 1, -1, 1, 0, -1};
int n, r, c, t, edc, edu[M], edv[M];
int ecnt, head[T], nxt[M], vet[M];
int qhead, qtail, que[T], f[T], in[T];
int stac[T], top, val[T], col[T], stamp, dfn[T], low[T], cnt;
bool instac[T];
struct Node {
int x, y, t;
bool operator <(const Node &ano) const {
return x < ano.x || x == ano.x && y < ano.y;
}
} a[N];
inline void add(int u, int v) {
edu[++edc] = u; edv[edc] = v;
vet[++ecnt] = v; nxt[ecnt] = head[u];
head[u] = ecnt;
}
inline void eadd(int u, int v) {
vet[++ecnt] = v; nxt[ecnt] = head[u];
head[u] = ecnt;
}
void tarjan(int u) {
dfn[u] = low[u] = ++stamp;
stac[++top] = u; instac[u] = true;
for (int e = head[u]; e; e = nxt[e]) {
int v = vet[e];
if (!dfn[v]) {
tarjan(v); low[u] = min(low[u], low[v]);
} else if (instac[v])
low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
col[u] = ++cnt; val[cnt] = u > r + c;
while (stac[top] != u) {
col[stac[top]] = cnt;
val[cnt] += (stac[top] > r + c);
instac[stac[top--]] = false;
}
instac[stac[top--]] = false;
}
}
int getid(int x, int y) {
int l = 1, r = n;
while (l <= r) {
int mid = (l + r) >> 1;
if (a[mid].x == x && a[mid].y == y) return mid;
else if (a[mid].x < x || a[mid].x == x && a[mid].y < y) l = mid + 1;
else r = mid - 1;
}
return -1;
}
int main() {
rd_test();
scanf("%d%d%d", &n, &r, &c);
for (int i = 1; i <= n; ++i)
scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].t);
sort(a + 1, a + 1 + n);
for (int i = 1; i <= n; ++i) {
//处理额外点对当前点的连边
add(a[i].x, r + c + i);
add(r + a[i].y, r + c + i);
//处理当前点对其它点的连边
if (a[i].t == 1) add(r + c + i, a[i].x);
else if (a[i].t == 2) add(r + c + i, r + a[i].y);
else {
for (int k = 0; k < 8; ++k) {
int x = a[i].x + dx[k], y = a[i].y + dy[k];
if (x >= 1 && x <= r && y >= 1 && y <= c) {
int id = getid(x, y);
if (id != -1) add(r + c + i, r + c + id);
}
}
}
}
//缩点
t = r + c + n;
for (int i = 1; i <= t; ++i)
if (!dfn[i])
tarjan(i);
ecnt = 0;
memset(head, 0, sizeof(head));
for(int i = 1; i <= edc; ++i)
if (col[edu[i]] != col[edv[i]]) {
eadd(col[edu[i]], col[edv[i]]);
++in[col[edv[i]]];
}
//拓扑排序
qhead = 0; qtail = -1;
for (int i = 1; i <= cnt; ++i)
if (!in[i]) {
que[++qtail] = i;
f[i] = val[i];
}
while (qhead <= qtail) {
int u = que[qhead++];
for (int e = head[u]; e; e = nxt[e]) {
int v = vet[e];
f[v] = max(f[v], f[u] + val[v]);
if (--in[v] == 0) que[++qtail] = v;
}
}
//统计答案
int ans = 0;
for (int i = 1; i <= cnt; ++i)
ans = max(ans, f[i]);
printf("%d\\n", ans);
Time_test();
return 0;
}
以上是关于P2403 [SDOI2010]所驼门王的宝藏的主要内容,如果未能解决你的问题,请参考以下文章