Loading

The 2024 ICPC Asia Nanjing Regional Contest

C - Topology

Solution

首先可以证明,对于节点数为 \(n\) 的树,整体拓扑序个数为 \(\dfrac{n!}{\prod_{i=1}^n size_i}\),其中树拓扑序是一种排列,使得每个节点的父亲都在它之前出现在排列中。简单证明一下:如果没有任何限制,那么方案数为 \(n!\),考虑子树 \(u\),那么 \(u\) 需要排在子树的最前面,所以乘以系数 \(\dfrac{1}{size_u}\).

下面的做法是基于从 \(v\) 开始从上到根的一条链进行考虑,我们钦定这条链上所有点在序列上的位置。为了复杂度,才从上至下 dp.

定义 \(f_u\) 为子树 \(u\) 的拓扑序个数,\(mul_u\) 为子树 \(u\) 所有节点的 \(size\) 之积。

定义 \(dp_{i,j}\) 为节点 \(i\) 在序列第 \(j\) 位,并且除了子树 \(i\) 其他部分都排序完成的方案数。这样的定义是由于我们需要至顶向下 dp.

考虑父节点 \(u\) 与子节点 \(v\),从 \(dp_{u,x}\) 转移到 \(dp_{v,y}\)。其实我们只需要考虑子树 \(u\) 除去子树 \(v\) 和节点 \(u\) 的部分:首先选出这一部分在序列中的位置,由于需要排在节点 \(u\) 之后,所以可选的位置只有 \(n-x-size_v\) 个,需要放置 \(size_u-size_v-1\) 个点,方案数为组合数

\[\binom{n-x-size_v}{size_u-size_v-1} \]

其次将这些节点内部拓扑序,方案数为

\[\dfrac{(size_u-size_v)!}{\prod_{i\in \text{subtree}(u),i\notin \text{subtree(v)}}size_i}\cdot \dfrac{size_u}{size_u-size_v} \]

右边的乘式是因为左边式子分母中的 \(size_u\) 实际上应该为 \(size_u-size_v\).

将两个部分相乘之结果记为 \(\rm coe\),于是得到

\[dp_{v,y}=\sum_{x=1}^{y-1}dp_{u,x}\cdot \text{coe}(u,x,v,y) \]

这个复杂度为 \(\mathcal O(n^3)\),考虑优化。发现 \(\rm coe\) 实际上和 \(y\) 没有关系,于是 \(x,y\) 可以分开枚举,复杂度降到 \(\mathcal O(n^2)\).

Code

传送门




I - Bingo

Solution

定义一行/列的 Bingo 数为该行/列的最大值。那么对于一种指派,最小的 Bingo 数就是所有行和列的 Bingo 数的最小值。先求最大值再求最小值是不容易的,我们考虑 min-max 容斥,计算最大值的最大值。

\[\min(S)=\sum_{T⊂S}(-1)^{|T|-1}\max(T) \]

\[\begin{aligned} \sum_p\min(S)&=\sum_p\sum_{T⊂S}(-1)^{|T|-1}\max(T)\\ &=\sum_{T⊂S}\sum_p(-1)^{|T|-1}\max(T) \end{aligned} \]

于是枚举子集 \(T\),假设选择了 \(x\)\(y\) 列,则一共选择了 \(c=mx+ny-xy\) 个元素,选择行列方案 \(\dbinom{n}{x}\dbinom{m}{y}\).

将序列从小到大排列,假设 \(c\) 个元素的最大值为 \(a_i\),则前 \(i-1\) 个元素要选择 \(c-1\) 个,然后 \(c\) 个元素全排列,剩下 \(nm-c\) 个元素全排列,最后的方案数即为 \(\dbinom{i-1}{c-1}c!(nm-c)!\).
于是

\[\sum_p\min(S)=\sum_{(x,y)}\sum_{i=c}^{nm}(-1)^{x+y-1}\cdot \dbinom{n}{x}\dbinom{m}{y}\cdot\dbinom{i-1}{c-1}c!(nm-c)!\cdot a_i \]

此时复杂度为 \(\mathcal O((nm)^2)\).

考虑如何快速计算 \(\dbinom{i-1}{c-1}c!\cdot a_i\) 这一部分。

\[\dbinom{i-1}{c-1}c!\cdot a_i=\frac{(i-1)!}{(c-1)!(i-c)!}\cdot c!\cdot a_i=\frac{(i-1)!}{(i-c)!}\cdot c\cdot a_i \]

考虑快速计算 \(\dfrac{(i-1)!}{(i-c)!}\cdot a_i\).

\(f_i=(i-1)!\cdot a_i\)\(g_j=\dfrac{1}{(-j)!}\),记 \(i+j=c\),则对 \(f,g\) 做卷积,结果 \(h_c\) 就是答案。不过为了将 \(j\) 修正成正数,不妨记 \(g_j=\dfrac{1}{(nm-j)!}\),记 \(i+j+nm=c+nm\),则对 \(f,g\) 做卷积,结果 \(h_{c+nm}\) 就是答案。

复杂度 \(\mathcal O(nm\log(nm))\).

Code

# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)

template <class T>
inline T read(const T sample) {
    T x=0; bool f=0; char s;
    while(!isdigit(s=getchar())) f|=(s=='-');
    for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
    return f? -x: x;
}
template <class T>
inline void write(T x) {
    static int writ[50], w_tp=0;
    if(x<0) putchar('-'), x=-x;
    do writ[++w_tp]=x-x/10*10, x/=10; while(x);
    while(putchar(writ[w_tp--]^48), w_tp);
}

# include <set>
# include <cmath>
# include <vector>
# include <cstring>
# include <iostream>
# include <algorithm>
using namespace std;
typedef long long ll;
typedef pair <int,int> pii;

const int MAXN = 2e5+5;
const int MAXM = 1e6+5;
const int mod = 998244353, G = 3;

int n, m, a[MAXN], L, lim, iG, rev[MAXM];
ll fac[MAXN], ifac[MAXN], f[MAXM], g[MAXM];

ll qkpow(ll x, int y=mod-2) {
	ll r = 1;
	while(y) {
		if(y&1) r=r*x%mod;
		x=x*x%mod; y>>=1;
	} return r;
}

void init() {
	fac[0] = 1;
	for(int i=1; i<=MAXN-5; ++i)
		fac[i] = fac[i-1]*i%mod;
	ifac[MAXN-5] = qkpow(fac[MAXN-5]);
	for(int i=MAXN-6; i>=0; --i)
		ifac[i] = ifac[i+1]*(i+1)%mod;
}

ll C(int x, int y) {
	return fac[x]*ifac[y]%mod*ifac[x-y]%mod;
}

void preWork() {
	int bit = 0; lim=1;
	iG = qkpow(G);
	while(lim<L+L-1) lim<<=1, ++bit;
	for(int i=0; i<lim; ++i)
		f[i] = g[i] = 0, 
		rev[i] = (rev[i>>1]>>1)|((i&1)<<bit-1);
}

void NTT(ll *f, bool opt) {
	int wn, w, tmp;
	for(int i=0; i<lim; ++i)
		if(i<rev[i]) swap(f[i], f[rev[i]]);
	for(int mid=1; mid<lim; mid<<=1) {
		wn = qkpow(opt?G:iG, (mod-1)/(mid<<1));
		for(int i=0; i<lim; i+=(mid<<1)) {
			w = 1;
			for(int j=0; j<mid; ++j, w=1ll*w*wn%mod) {
				tmp = f[i+j+mid]*w%mod;
				f[i+j+mid] = (f[i+j]-tmp+mod)%mod;
				f[i+j] = (f[i+j]+tmp)%mod;
			}
		}
	}
}

void noSananoLife() {
	n=read(9), m=read(9), L=n*m+1;
	for(int i=1; i<L; ++i) 
		a[i] = read(9);
	sort(a+1, a+L);
	preWork();
	for(int i=1; i<L; ++i)
		f[i] = fac[i-1]*a[i]%mod;
	f[0] = 0;
	for(int i=0; i<L; ++i)
		g[i] = ifac[L-1-i];
	NTT(f, 1), NTT(g, 1);
	for(int i=0; i<lim; ++i)
		f[i] = f[i]*g[i]%mod;
	NTT(f, 0);
	int _inv = qkpow(lim);
	for(int i=0; i<lim; ++i)
		f[i] = f[i]*_inv%mod;
	ll ans = 0;
	for(int x=0; x<=n; ++x)
		for(int y=0; y<=m; ++y) {
			if(x==0 && y==0) continue;
			int c = m*x+n*y-x*y;
			ll tmp = C(n,x)*C(m,y)%mod*fac[L-1-c]%mod*c%mod;
			tmp = tmp*f[c+L-1]%mod;
			if((x+y-1)&1) ans = (ans-tmp+mod)%mod;
			else ans = (ans+tmp)%mod;
		}
	print(ans, '\n');
}

int main() {
	init();
	for(int T=read(9); T; --T)
		noSananoLife();
	return 0;
}



M - Ordainer of Inexorable Judgment

Solution

先抛开 \((x_0,y_0)\) 不管,我们计算那维莱特转一圈的时间内,有多少时间可以喷到多边形。从多边形整体的顺时针方向开始逆时针转,可以发现,我们只需要计算那维莱特的水柱第一次碰到多边形的角度 \(\alpha\),和那维莱特的水柱在这一圈最后碰到多边形的角度 \(\beta\).

设以 \((0,0)\) 为圆心,\(d\) 为半径的圆为圆 L。对于多边形的每个点 \((x_i,y_i)\),向圆 L 做两条切线,这两条切线分别代表了那维莱特第一次碰到和离开这个点的水柱(两条水柱的左边界和右边界)。由于一条水柱的中间射线和左边界右边界都平行,我们使用中间射线和 \(x\) 正半轴的夹角来表示一条水柱。于是对于 \(n\) 个点,我们可以计算出 \(2n\) 条水柱。

现在只需要找出 \(\alpha,\beta\)。注意到多边形和圆 L 没有交点,所以从 \(\beta\) 逆时针转到 \(\alpha\) 的角度一定 \(>180^\circ\)。所以将 \(2n\) 条水柱逆时针排序,一定只有一组解满足这个条件,我们就找到了答案。

Code

# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)

template <class T>
inline T read(const T sample) {
    T x=0; bool f=0; char s;
    while(!isdigit(s=getchar())) f|=(s=='-');
    for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
    return f? -x: x;
}
template <class T>
inline void write(T x) {
    static int writ[50], w_tp=0;
    if(x<0) putchar('-'), x=-x;
    do writ[++w_tp]=x-x/10*10, x/=10; while(x);
    while(putchar(writ[w_tp--]^48), w_tp);
}

# include <set>
# include <cmath>
# include <vector>
# include <cstring>
# include <iostream>
# include <algorithm>
using namespace std;
typedef long long ll;
typedef pair <int,int> pii;

const int MAXN = 2e5+5;
const int MAXM = 1e6+5;
const int mod = 998244353, G = 3;
const double pi = acos(-1.0);
const double eps = 1e-9;

int sgn(double x) {
    return (fabs(x)<=eps)? 0: (x<0? -1: 1);
}

struct vec {
    double x, y;
    vec(const double X=0, const double Y=0): x(X), y(Y) {}
    vec(double r, double alpha, int _): x(r*cos(alpha)), y(r*sin(alpha)) {}

    vec operator + (const vec& t) const { return vec(x+t.x, y+t.y); } 
	vec operator - (const vec& t) const { return vec(x-t.x, y-t.y); }
    friend double dot(const vec& a,const vec& b) { return a.x*b.x+a.y*b.y; }
    double len() const { return sqrt(dot(*this, *this)); }
    double angle() const {
        double ang = atan2(y, x);
        if(sgn(ang)<0) ang += pi*2;
        return ang;
    }
} st, now;
double d, t;
int n;
vector <double> seq;

int main() {
    int x_0, y_0;
    n=read(9), x_0=read(9), y_0=read(9);
    d=read(9), t=read(9);
    st = vec(x_0, y_0);
    for(int i=1; i<=n; ++i) {
        x_0=read(9), y_0=read(9);
        now = vec(x_0, y_0);
        double phi = acos(d/now.len());
        seq.push_back((now-vec(d, now.angle()+phi, 1)).angle());
        seq.push_back((now-vec(d, now.angle()-phi, 1)).angle());
    }
    double angs = st.angle();
    for(int i=0; i<n*2; ++i) {
        seq[i] -= angs;
        if(seq[i] < 0) seq[i] += pi*2;
    }
    sort(seq.begin(), seq.end());
    bool flag = true;
    double L, R;
    if(seq[0]+pi > seq.back()) {
        L=seq[0], R=seq.back();
        flag = false;
    } else {
        for(int i=0; i<n*2-1; ++i) {
            if(seq[i]+pi < seq[i+1]) {
                L=seq[i], R=seq[i+1];
                break;
            }
        }
    }
    double q = floor(t/(pi*2));  
    double r = fmod(t, pi*2);    
    double ans = q*(R-L);  
    if(r > L) ans += r-L;
    if(r > R) ans -= r-R;
    ans = (flag? t-ans: ans);
    printf("%.9f\n", ans); 
	return 0;
}



F - Subway

Solution

使用 \((i,j)\) 表示当前在第 \(i\) 个站,在第 \(j\) 条线路上。这个问题实际上就是计算最短路。问题在于,这张图的边数是 \(n^2\) 条级别的,且边数的复杂度主要在于同站换线路操作。于是我们的核心思路就是降低同站换线路的边数。

假设我们从 \((i,x)\) 开始进行同站转移,可以转移到 \(y,z\) 两条线路。不妨设 \(a_y<a_z\)。于是线路 \(y\) 的转移 \(d_{(x,y)}=dis[(i,x)]+b_x\cdot a_y\),线路 \(z\) 的转移 \(d_{(x,z)}=dis[(i,x)]+b_x\cdot a_z\)

我们可以证明,仅考虑同站转移时,从 \(z\) 转移到 \(y\) 一定不优。这是因为 \(d_{(z,y)}=dis[(i,z)]+b_z\cdot a_y=d_{(x,z)}+b_z\cdot a_y\)。由于 \(a_y<a_z\),所以 \(d_{(x,y)}<d_{(x,z)}<d_{(z,y)}\)。这就意味着,从 \((i,x)\) 转移,我们只需要转移到同站未出队列的 \(a_y\) 最小的 \(y\) 进行转移。

同站转移使用李超树进行维护即可。

Code

# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)

template <class T>
inline T read(const T sample) {
    T x=0; bool f=0; char s;
    while(!isdigit(s=getchar())) f|=(s=='-');
    for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
    return f? -x: x;
}
template <class T>
inline void write(T x) {
    static int writ[50], w_tp=0;
    if(x<0) putchar('-'), x=-x;
    do writ[++w_tp]=x-x/10*10, x/=10; while(x);
    while(putchar(writ[w_tp--]^48), w_tp);
}

# include <set>
# include <map>
# include <tuple>
# include <queue>
# include <vector>
# include <cstring>
# include <iostream>
# include <algorithm>
using namespace std;
typedef long long ll;
typedef pair <int,int> pii;

const int MAXN = 2e5+5;
const ll infty = 1e18;

map <pii, bool> vis;
map <pii, ll> dis;
map <pii, pii> nxt;
vector <int> value;
ll ans[MAXN];
int n, k, a[MAXN], b[MAXN], rt[MAXN];
struct Node {
    int path, val;
    Node() {}
    Node(int _p=0, int _v=0): path(_p), val(_v) {}
    bool operator < (const Node& t) const {
        return (val^t.val)? (val<t.val): (path<t.path);
    }
};
struct nodeq {
    int pos, path; ll dis;
    nodeq() {}
    nodeq(int _pos=0, int _p=0, ll _d=0): pos(_pos), path(_p), dis(_d) {}
    bool operator < (const nodeq& t) const {
        return dis > t.dis;
    }
};
set <Node> cho[MAXN];
priority_queue <nodeq> q;

struct lichaoTree {
    struct line {
        ll k, b;
        line(ll _k=0, ll _b=0) {
            k=_k, b=_b;
        }
        ll calc(int x) { return k*value[x]+b; }
    };
    static const int MAXM = MAXN*30;
    int idx, son[MAXM][2];
    line t[MAXM];
    void ins(int &o, int l, int r, line now) {
        if(!o) return t[o=++idx] = now, void();
        int mid = l+r>>1;
        ll La = t[o].calc(l), Ra = t[o].calc(r), Lb = now.calc(l), Rb = now.calc(r);
        if(La<=Lb && Ra<=Rb) return;
        if(La>=Lb && Ra>=Rb) return t[o]=now, void();
        if(t[o].calc(mid) > now.calc(mid)) swap(t[o], now);
        if(t[o].calc(l) < now.calc(l))
            ins(son[o][1], mid+1, r, now);
        else ins(son[o][0], l, mid, now);
    }
    ll ask(int o, int l, int r, int p) {
        if(!o) return infty;
        int mid = l+r>>1;
        ll ret = t[o].calc(p);
        if(p<=mid) ret=min(ret, ask(son[o][0], l, mid, p));
        else ret=min(ret, ask(son[o][1], mid+1, r, p));
        return ret;
    }
} Seg;

int main() {
    n = read(9), k = read(9);
    for(int i=1; i<=k; ++i) 
        value.push_back(a[i]=read(9));
    for(int i=1; i<=k; ++i) b[i]=read(9);
    vector <int> scan;
    for(int i=1; i<=k; ++i) {
        int all = read(9); scan.clear();
        for(int j=1; j<=all*2-1; ++j)
            scan.push_back(read(9));
        for(int j=1; j<all; ++j) 
            nxt[make_pair(scan[j*2-2], i)] = make_pair(scan[j<<1], scan[j*2-1]);   
        for(int j=1; j<=all; ++j)
            cho[scan[j*2-2]].insert(Node(i, a[i]));
    }
    for(auto it: cho[1]) {
        pii now = make_pair(1, it.path);
        dis[now] = 0;
        q.push(nodeq(1, it.path, 0));
    }
    sort(value.begin(), value.end());
    value.erase(unique(value.begin(), value.end()), value.end());
    int lim = value.size()-1;
    cho[1].clear();
    while(! q.empty()) {
        nodeq now = q.top(); q.pop();
        pii u = make_pair(now.pos, now.path);
        if(vis.find(u)!=vis.end()) continue;
        vis[u] = true;
        if(now.pos != 1) {
            int val = lower_bound(value.begin(), value.end(), a[now.path])-value.begin();
            ll ret = Seg.ask(rt[now.pos], 0, lim, val);
            if(dis.find(u) == dis.end()) dis[u]=ret;
            else dis[u] = min(dis[u], ret);
            Seg.ins(rt[now.pos], 0, lim, {b[now.path], dis[u]});
            cho[now.pos].erase(Node(now.path, a[now.path]));
            if(! cho[now.pos].empty()) {
                Node tmp = *cho[now.pos].begin();
                val = lower_bound(value.begin(), value.end(), a[tmp.path])-value.begin();
                ll ret = Seg.ask(rt[now.pos], 0, lim, val);
                pii v = make_pair(now.pos, tmp.path);
                if(dis.find(v)==dis.end() || dis[v]>ret) {
                    dis[v] = ret;
                    q.push(nodeq(v.first, v.second, dis[v]));
                }
            }
        }
        if(nxt.find(u) == nxt.end()) continue;
        pii tmp = nxt[u];
        pii v = make_pair(tmp.first, now.path);
        if(dis.find(v)==dis.end() || (dis[v]>dis[u]+tmp.second)) {
            dis[v] = dis[u]+tmp.second;
            q.push(nodeq(v.first, v.second, dis[v]));
        }
    }
    for(int i=1; i<=n; ++i) ans[i]=infty;
    for(auto it: dis) 
        ans[it.first.first] = min(ans[it.first.first], it.second);
    for(int i=2; i<=n; ++i) 
        print(ans[i], ' '); puts("");
    return 0;
}



posted @ 2025-03-08 20:44  ShyGray  阅读(155)  评论(0)    收藏  举报