时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
牛牛有一张 n 个点,m 条边的无向图,每条边有一个边权 wiw_iwi
我们定义一条路径的权值是这个路径包含的边的权值的最大值。
定义 d(u,v) 表示在无向图中点 u 能到达点 v 的所有路径中权值最小的路径的权值
现在牛牛给你 q 次询问,每次询问给出一个 L ,询问 ∑i=1n∑j=i+1n[d(i,j)≤L]\sum_{i=1}^n \sum_{j=i+1}^n [d(i,j) \leq L]∑i=1n∑j=i+1n[d(i,j)≤L]。其中 [C] 表示当命题 C 为真的时候为 1 否则为 0。比如 [出题人很弱]=1,[1≥2]=0[\text{出题人很弱}]=1,[1 \geq 2] = 0[出题人很弱]=1,[1≥2]=0。
为了防止输入过大,选手需要在自己的程序内生成要输入的所有数据,可以参考如下代码:
unsigned int SA, SB, SC; int n, m, q, LIM;
unsigned int rng61(){
SA ^= SA << 16;
SA ^= SA >> 5;
SA ^= SA << 1;
unsigned int t = SA;
SA = SB;
SB = SC;
SC ^= t ^ SA;
return SC;
}
void gen(){
scanf("%d%d%d%u%u%u%d", &n, &m, &q, &SA, &SB, &SC, &LIM);
for(int i = 1; i <= m; i++){
u[i] = rng61() % n + 1;
v[i] = rng61() % n + 1;
w[i] = rng61() % LIM;
}
for(int i = 1; i <= q; i++){
L[i] = rng61() % LIM;
}
}
生成的 ui,vi,wiu_i,v_i,w_iui,vi,wi 表示第 条边的属性,LiL_iLi 表示第 次询问输入的数。
一行七个正整数
一行,表示所有答案的异或和。
示例1
复制5 7 5 480944053 701657892 339027200 10
5 7 5 480944053 701657892 339027200 10
复制1
1
五次询问分别是 7,4,0,4,9
答案分别是 3,3,1,3,3
异或后答案是 1。
1≤n≤105,1≤m,q≤5×105,1≤LIM≤1091 \leq n \leq 10^5,1 \leq m,q \leq 5 \times 10^5,1 \leq LIM \leq 10^91≤n≤105,1≤m,q≤5×105,1≤LIM≤109
题目大意:
输出所有询问的异或和。
解法:
首先MST能够使路径包含的边的权值最大值最小,这是容易观察到的性质,那么这个无向图就简化成了若干棵树。如果把树边都删掉,按照边权从小到大加回去,每条边的贡献恰好是两个点集大小的乘积,同时也能够满足第二条性质,这样求MST的过程中就能够解决所有的问题,剩下询问的答案可以离线也预处理前缀和再二分。
Accepted code
#pragma GCC optimize(3)
#include
#include
using namespace std;
#define sc scanf
#define Min(x, y) x = min(x, y)
#define Max(x, y) x = max(x, y)
#define ALL(x) (x).begin(),(x).end()
#define SZ(x) ((int)(x).size())
#define pir pair
#define MK(x, y) make_pair(x, y)
#define MEM(x, b) memset(x, b, sizeof(x))
#define MPY(x, b) memcpy(x, b, sizeof(x))
#define lowbit(x) ((x) & -(x))
#define P2(x) ((x) * (x))
typedef long long ll;
const int Mod = 1e9 + 7;
const int N = 1e5 + 100;
const int M = 5e5 + 100;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
inline ll dpow(ll a, ll b){ ll r = 1, t = a; while (b){ if (b & 1)r = (r*t) % Mod; b >>= 1; t = (t*t) % Mod; }return r; }
inline ll fpow(ll a, ll b){ ll r = 1, t = a; while (b){ if (b & 1)r = (r*t); b >>= 1; t = (t*t); }return r; }
unsigned int SA, SB, SC; int n, m, q, LIM;
unsigned int rng61(){
SA ^= SA << 16;
SA ^= SA >> 5;
SA ^= SA << 1;
unsigned int t = SA;
SA = SB;
SB = SC;
SC ^= t ^ SA;
return SC;
}
struct node
{
ll w;
int u, v;
bool operator < (const node &oth) const {
return w < oth.w;
}
}a[M];
int f[N], sz[N];
ll val[M], pre[M];
void Init() {
for (int i = 1; i <= n; i++)
f[i] = i, sz[i] = 1;
}
int Find(int x) {
while (x != f[x])
x = f[x];
return x;
}
void Merge(int x, int y) {
x = Find(x);
y = Find(y);
if (x == y)
return;
if (sz[x] > sz[y])
swap(x, y);
f[x] = y, sz[y] += sz[x];
}
int main()
{
sc("%d%d%d%u%u%u%d", &n, &m, &q, &SA, &SB, &SC, &LIM);
Init();
for (int i = 1; i <= m; i++) {
int u = rng61() % n + 1;
int v = rng61() % n + 1;
ll w = rng61() % LIM;
a[i] = { w, u, v };
}
sort(a + 1, a + m + 1);
int k = 0, pos = 0;
for (int i = 1; i <= m; i++) {
if (k == n - 1)
break;
int u = a[i].u, v = a[i].v;
if (Find(u) == Find(v))
continue;
pos++;
pre[pos] = pre[pos - 1] + 1ll * sz[Find(u)] * sz[Find(v)]; // 点对数前缀和
val[pos] = a[i].w;
Merge(u, v);
}
ll ans = 0;
for (int i = 1; i <= q; i++) {
int qi = rng61() % LIM;
int it = upper_bound(val + 1, val + pos + 1, qi) - val; // 二分位置
ans ^= pre[it - 1];
}
printf("%lld\n", ans);
return 0; // 改数组大小!!!用pair记得改宏定义!!!
}