1. 程式人生 > >【NOIP2018模擬賽2018.10.29】

【NOIP2018模擬賽2018.10.29】

在這裡插入圖片描述
正解 邊帶權並查集
具體思路,跟一般的邊帶權並查集一樣,一邊加入並查集,維護每個點到其根的dis,還沒加入的邊預設正確,在加入的邊中進行判斷是否合法。
 可以知道,若一次詢問兩個士兵 (設為l,r) 在一個集合中,為了滿足前面的要求,又要滿足這次的要求,必須 dis[l] + d == dis[r] ,dis[l]代表l點到根的距離,dis[r]代表r到根的距離,若不滿足則一定不合法。
 如果兩個士兵不在同一個並查集中,為了滿足dis合法,需滿足上面的要求,於是可以用下圖理解:
醜陋的圖片
於是可以知道dis[fy] = dis[x] + d - dis[y] 合法。
這樣統計完了後,直接列舉每個點,分別取此點並查集中的最大值和最小值,列舉每個集合,答案就是最大值與最小值相差最大的那個值。
程式碼:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pt putchar
#define gc getchar
#define ko pt(' ')
#define ex pt('\n')
const int MAXN = 1e5 + 5;
const int INF = 999999999;
int n,m,fa[MAXN];
int val[MAXN],Max[MAXN],Min[MAXN];
void in(int &x)
{
	int num = 0,f = 1; char
ch = gc(); while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = gc();} while(ch >= '0' && ch <= '9') {num = (num<<3) + (num<<1) + (ch-'0'); ch = gc();} x = num*f; } void out(int x) { if(x < 0) x = -x,pt('-'); if(x > 9) out(x/10); pt(x % 10 + '0'); } int find(int
x) { if(fa[x] == x) return x; else { int tmp = find(fa[x]); val[x] += val[fa[x]]; fa[x] = tmp; return tmp; } } int main() { in(n); in(m); // if(m == 0) {cout << 0; return 0;} for(int i = 1;i <= n;i++) fa[i] = i; for(int i = 1;i <= m;i++) { int x,y,d; in(x),in(y),in(d); int fx = find(x),disx = val[x]; int fy = find(y),disy = val[y]; if(fx == fy) { if(d + disx != disy){ printf("impossible"); return 0; } } else { fa[fy] = fx; val[fy] = disx + d - disy; } } int ans = 0; for(int i = 1;i <= n;i++) Max[i] = -INF,Min[i] = INF; for(int i = 1;i <= n;i++) { int root = find(i); Max[root] = max(Max[root],val[i]); Min[root] = min(Min[root],val[i]); } for(int i = 1;i <= n;i++) if(Min[i] != INF) ans = max(ans,Max[i] - Min[i]); out(ans); return 0; } /* 3 3 1 2 1 2 3 1 1 3 2 */

在這裡插入圖片描述在這裡插入圖片描述
最短路+幾乎不能算DP的DP
 先跑一邊最短路,求出st到各個節點的dis,然後用兩個陣列:
  f[i]表示從st到i點的最短路條數,g[i]表示從i點到st與ed的最短路上的最短路條數。
明顯不考慮相不相遇,所有走的方案數等於g[ed]2。應該很好理解(從st走有g[ed]種選擇,從ed走有g[ed]種選擇,根據乘法原理)
考慮不合法情況有哪些:

邊相遇

 當有兩個(u為當前點,v為與之相連點) 在最短路上的點,v到st的距離乘以2大於st到ed的距離, u到st的距離乘以2小於到st到ed的距離,那麼他們一定可以在這條邊上相遇(可以畫個圖理解一下),那麼方案數就應該減去(f[u]*g[v])2。(同上乘法原理)

點相遇

 兩人到一點上相遇的情況就簡單了,只要st到當前點i == ed到當前點i距離相等即可,即st到點i距離的兩倍等於st到ed距離即可。同樣減去(f[i]*g[i])2
減去後即為答案。
上程式碼:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pt putchar
#define gc getchar
#define ko pt(' ')
#define ex pt('\n')
const int MAXN = 2e5 + 5;
const int MOD = 1e9 + 7;
int n,m,st,ed;
ll ans = 0,INF;
struct edge
{
	int next,to; ll w;
}e[MAXN<<1];
int head[MAXN<<1],cnt = 0,idx[MAXN];
ll f[MAXN],g[MAXN],dis[MAXN];
bool vis[MAXN];
void add(int u,int v,ll val)
{
	e[++cnt].next = head[u]; e[cnt].to = v; e[cnt].w = val; head[u] = cnt;
	e[++cnt].next = head[v]; e[cnt].to = u; e[cnt].w = val; head[v] = cnt;
}
void in(int &x)
{
	int num = 0,f = 1; char ch = gc();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = gc();}
	while(ch >= '0' && ch <= '9') {num = (num<<3) + (num<<1) + (ch-'0'); ch = gc();}
	x = num*f;
}
void lin(ll &x)
{
	ll num = 0,f = 1; char ch = gc();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = gc();}
	while(ch >= '0' && ch <= '9') {num = (num<<3) + (num<<1) + (ch-'0'); ch = gc();}
	x = num*f;
}
void out(ll x)
{
	if(x < 0) x = -x,pt('-');
	if(x > 9) out(x/10);
	pt(x % 10 + '0');
}

priority_queue<pair<ll,int> > q;

void dijkstra()
{
	memset(dis,0x3f,sizeof dis);
	memset(vis,0,sizeof vis);
	INF = dis[0]; dis[st] = 0; 
	q.push(make_pair(0,st));
	while(!q.empty())
	{
		int x = q.top().second; q.pop();
		if(vis[x]) continue; vis[x] = 1;
		for(int i = head[x];i;i = e[i].next)
		{
			int to = e[i].to,w = e[i].w;
			 if(dis[to] > dis[x] + w){
			 	dis[to] = dis[x] + w;
			 	q.push(make_pair(-dis[to],to));
			 }
		}
	}
}
bool cmp(int a,int b){
	return dis[a] < dis[b];
}

int main()
{
	in(n); in(m);
	in(st); in(ed);
	for(int i = 1;i <= m;i++)
	{
		int u,v; ll val;
		in(u),in(v),lin(val);
		add(u,v,val);
	}
	dijkstra();
	for(int i = 1;i <= n;i++) idx[i] = i;
	sort(idx+1,idx+n+1,cmp);
	f[st] = 1,g[ed] = 1;
	for(int i = 1;i <= n;i++)
	{
		int x = idx[i];
		for(int j = head[x];j;j = e[j].next)
		{
			int to = e[j].to; ll w = e[j].w;
			if(dis[to] == dis[x] + w) 
				f[to] = (f[x] + f[to]) % MOD;
		}
	}
	for(int i = n;i >= 1;i--)
	{
		int x = idx[i];
		for(int j = head[x];j;j = e[j].next)
		{
			int to = e[j].to; ll w = e[j].w;
			if(dis[to] == dis[x] - w)
				g[to] = (g[x] + g[to]) % MOD;
		}
	}
	
	ans = f[ed]*f[ed] % MOD;
	
	for(int i = 1;i <= n;i++)
		for(int j = head[i];j;j = e[j].next)
		{
			int to = e[j].to; ll w = e[j].w;
			if(dis[to] == dis[i] + w)
				if((dis[to] << 1) > dis[ed] && (dis[i] << 1) < dis[ed])
					ans = ((ans - f[i] % MOD * g[to] % MOD * f[i] % MOD * g[to] % MOD) + MOD) % MOD;
		}
	
	for(int i = 1;i <= n;i++)
		if((dis[i] << 1) == dis[ed])
			ans = ((ans - f[i] % MOD * g[i] % MOD * f[i] % MOD * g[i] % MOD) % MOD + MOD) % MOD;
	out(ans % MOD);
	return 0;
}
/*
4 4
1 3
1 2 1
2 3 1
3 4 1
4 1 1
*/

在這裡插入圖片描述
尤拉序 + 樹狀陣列維護鏈相交

首先尤拉序

概念:

指從根結點出發,按dfs的順序在繞回原點所經過所有點的順序。

這裡用尤拉序處理LCA,方便進行樹狀陣列維護。
首先要說這道題的一個關鍵定理
 一條鏈與另一條鏈相交,充要條件是:一條鏈上的深度最淺的點(就是LCA)在另一條鏈上
於是可以想到這道題樹狀陣列的運用就是在尤拉序上維護這條鏈上有多少個其他鏈的LCA
這樣將每一條鏈上的其他鏈的LCA數量加起來再減去會重複的每個LCA數量(設為n)的平方與
C(2,n)即為答案。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pt putchar
#define gc getchar
#define ko pt(' ')
#define ex pt('\n')
const int MAXN = 2e6 + 5;
int n,m;
ll ans = 0;
struct edge
{
	int next,to;
}e[MAXN<<1];
struct question
{
	int u,v,up;
}node[MAXN<<1];
int head[MAXN<<1],cnt = 0;
bool vis[MAXN<<1];
int dep[MAXN<<1],fa[MAXN],pa[MAXN],tot[MAXN];
int enter[MAXN],getout[MAXN],sum[MAXN];
vector<int> link[MAXN],idx[MAXN];
void add(int u,int v)
{
	e[++cnt].next = head[u]; e[cnt].to = v; head[u] = cnt;
	e[++cnt].next = head[v]; e[cnt].to = u; head[v] = cnt;
}
void in(int &x)
{
	int num = 0,f = 1; char ch = gc();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = gc();}
	while(ch >= '0' && ch <= '9') {num = (num<<3) + (num<<1) + (ch-'0')