P2403 [SDOI2010]所驼门王的宝藏
题意:
R * C的地图上有n个宝藏,给你n个宝藏的坐标,每个宝藏的位置上还有一个传送门,传送门有三种类型,1.可以传送到同行的其他宝藏位置,2.可以传送到同列的其他宝藏位置 3.可以传送到该点周围的八个位置
你可以在任意一个宝藏处开始,问最多获得多少宝藏?
题解:
如果我们直接按照题意要求建边,(暴力建边,第1类门和每行其他门建边,第2,3类门同理),会得到一个有向有环图,对于环我们可以用tarjan进行缩点,然后得到DAG,然后就是在DAG直接dp求就行(拓扑排序)。
dp[v]=max(dp[u]+val[v])dp[v]=max(dp[u]+val[v] )dp[v]=max(dp[u]+val[v])
但是数据告诉我们不会这么简单,N<=100000,R<=1000000,C<=1000000
边的数量巨大,可达O(n2n^2n2),所以需要一个巧妙的建边方法来降低复杂度
原始的建边是两两之间建立练习,避免不了是O(n2n^2n2),现在我们可以这样想,对于每行,每列都建立一个额外的点,然后让所有点与这个额外的点建立联系。
第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#elsestartTime = clock(); //计时开始freopen("sotomon.in","r",stdin);#endif
}
void Time_test(){#ifdef ONLINE_JUDGE#elseendTime = 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;
}