比赛-ZR DAY2 (05 Aug, 2018)
A. 占领地区
离线处理。把主对角线与交矩形上方的那条边所在的直线的交点记录下来。这样对于每条副对角线,查询与它相交的主对角线这个问题就变成了一个区间求和问题。前缀和处理一下就可以了。应把副对角线分成两类讨论,因为可以发现在左上和右下的两条对角线“映射”到矩形上方的那条边所在的直线时,可能会重叠。做题的时候意识模糊去排了个序,其实没有必要,直接枚举位置就好了(桶排序的思想)。
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long ll;
const int _N = 110000;
struct data {
int l, r;
data(int l = 0, int r = 0):
l(l), r(r) { }
bool operator == (const data &tmp) const
{
return l == tmp.l && r == tmp.r;
}
bool operator < (const data &tmp) const
{
return l != tmp.l ? l < tmp.l : r < tmp.r;
}
};
ll N, M;
int SX[_N*2][2], len[2];
bool A[3100][3100], X[_N*2];
vector<data> Q[2];
void Update(int x, int y)
{
if (1 <= x && x <= N && 1 <= y && y <= N) A[x][y] = true;
return;
}
void Fun1()
{
int i, j, t;
ll ans = 0;
for (i = 1; i <= M; ++i) {
int x, y;
scanf("%d%d", &x, &y);
for (t = 1; t <= N; ++t) Update(t, x+y-t), Update(t, -(x-y-t));
}
for (i = 1; i <= N; ++i)
for (j = 1; j <= N; ++j)
if (A[i][j]) ++ans;
printf("%lld\n", N*N-ans);
}
void Fun2()
{
ll a, b, ans = 0;
scanf("%lld%lld", &a, &b);
ans += a+b >= N+1 ? N-(a+b-N)+1 : a+b-1;
ans += b-a >= 0 ? N+(a-b-1)+1 : -(a-b-N);
printf("%lld\n", N*N-ans+1);
}
void Fun3()
{
int EX = N+100, i, j, t;
ll ans = 0;
for (i = 1; i <= M; ++i) {
int a, b, g1_x, g1_y, g2_x, g2_y, k;
scanf("%d%d", &a, &b);
t = -(a-b-1);
X[t+EX] = true;
if (1 <= a+b-1 && a+b-1 <= N) {
g1_x = a+b-1, g1_y = 1;
g2_x = 1, g2_y = a+b-1;
k = 0;
} else {
g1_x = N, g1_y = a+b-N;
g2_x = a+b-N, g2_y = N;
k = 1;
}
Q[k].push_back(data(-(g1_x-g1_y-1), -(g2_x-g2_y-1)));
}
for (i = 1; i <= N+EX; ++i) {
SX[i][i&1] = SX[i-1][i&1]+X[i];
SX[i][i&1^1] = SX[i-1][i&1^1];
if (!X[i]) continue;
t = 1-(i-EX)+1;;
if (1 <= t && t <= N)
ans += N-t+1;//-----------
else if (1 <= i-EX && i-EX <= N)
ans += N-(i-EX)+1;//--------
}
for (i = 0; i <= 1; ++i) {
sort(Q[i].begin(), Q[i].end());
len[i] = unique(Q[i].begin(), Q[i].end())-Q[i].begin();
}
for (i = 0; i <= 1; ++i) {
for (j = len[i]-1; j >= 0; --j) {
data p = Q[i][j];
ans += p.r;//---------
t = (p.r+EX)&1;
ans -= SX[p.r+EX][t]-SX[p.l-1+EX][t];//----------
}
}
printf("%lld\n", N*N-ans);
return;
}
int main()
{
scanf("%lld%lld", &N, &M);
if (M <= 3005 && N <= 3005) { Fun1(); return 0; }
if (M == 1) { Fun2(); return 0; }
Fun3();
return 0;
}
B. 配对
对每条边计算贡献。期望 = 权值 * (有贡献的方案 / 总方案) 。设一条边权 \(w\) , 连接了两个大小分别为 \(x\) 和 \(y\) 的联通块,在大小为 \(x\) 的块中选 \(i\) 个男生 \(j\) 个女生,分析一波可以得出:
\[e = w \cdot \left( ^{m}_{i} \right) \cdot \left( ^{m}_{j} \right) \cdot x^{i+j} \cdot y^{m - i + m - j} \cdot (min(i, m - j) + min(j, m - i))
\]
设 \(t = i + j\) ,可以把多个期望合在一块儿贡献……各种预处理,时间复杂度降为 \(O(nm)\) 。
#include <cstdio>
#include <vector>
#include <algorithm>
#include <ctype.h>
using namespace std;
#define SC(a, b) (static_cast<a>(b))
typedef long long ll;
const int _N = 5200;
const ll MOD = 1e9+7;
struct edge {
int v, w;
edge(int v = 0, int w = 0):
v(v), w(w) { }
};
vector<edge> G[_N];
int ans, N, M, Siz[_N], Mon[_N][_N], f[_N], J[_N], inv[_N];
void getnum(int &num)
{
char tt;
while (!isdigit(tt = getchar()));
num = tt-'0';
while (isdigit(tt = getchar()))
num = (num<<3)+(num<<1)+tt-'0';
return;
}
inline int mul(int a, int b) { return SC(int, SC(ll, a)*b%MOD); }
inline int add(int a, int b)
{
ll tmp = SC(ll, a)+b;
return SC(int, tmp > MOD ? tmp-MOD : tmp);
}
int mont(int a, int b)
{
a %= MOD;
int t = 1;
while (b) {
if (b & 1) t = mul(t, a);
b >>= 1, a = mul(a, a);
}
return t;
}
void DFS(int p, int dad)
{
Siz[p] = 1;
for (int i = G[p].size()-1; ~i; --i) {
edge g = G[p][i];
if (g.v ^ dad) {
DFS(g.v, p);
Siz[p] += Siz[g.v];
for (int j = 0; j <= (M<<1); ++j) {
int tmp = mul(mul(Mon[Siz[g.v]][j], Mon[N-Siz[g.v]][(M<<1)-j]), f[j]);
ans = add(ans, mul(tmp, g.w));
}
}
}
return;
}
inline int GetC(int dn, int up) { return mul(mul(J[dn], inv[dn-up]), inv[up]); }
int main()
{
int i, j;
getnum(N), getnum(M);
for (i = 1; i < N; ++i) {
int a, b, c;
getnum(a), getnum(b), getnum(c);
G[a].push_back(edge(b, c)), G[b].push_back(edge(a, c));
}
for (i = 1; i <= N; ++i) {
Mon[i][0] = 1;
for (j = 1; j <= (M<<1); ++j)
Mon[i][j] = mul(Mon[i][j-1], i);
}
J[0] = 1;
for (i = 1; i <= M; ++i) J[i] = mul(J[i-1], i);
inv[M] = mont(J[M], MOD-2);
for (i = M-1; i >= 0; --i) inv[i] = mul(inv[i+1], i+1);
for (i = 0; i <= M; ++i)
for (j = 0; j <= M; ++j)
f[i+j] = add(f[i+j], mul(add(min(i, M-j), min(j, M-i)), mul(GetC(M, i), GetC(M, j))));
DFS(1, -1);
printf("%d\n", ans);
return 0;
}
C. 导数卷积
太毒瘤了,不会做。极度痛苦地打完 \(O(n ^ 2 \cdot log\ n)\) 的 NTT 想拿 40 暴力分,结果 ZROJ 太快让 \(O(n ^ 3)\) 的纯暴力也过去了,血亏。然后 10 分的特殊样例没来得及看 Orz 。正解不太懂……