W
e
l
c
o
m
e
: )

赛生新

8题榜首,what can i say,被一万个人 n+1

A

子序列匹配,容易做到 \(O(nm)\)

Code
#include <bits/stdc++.h>
using namespace std;
char s[105];
//CCNU HUST HZAU WHU
signed main(){
	scanf("%s", s+1);
	int n=strlen(s+1);
	long long f[4]={0, 0, 0, 0};
	for(int i=1; i<=n; ++i){
		if(s[i]=='C'){
			f[1]+=f[0];++f[0];
		}
		else if(s[i]=='N') f[2]+=f[1];
		else if(s[i]=='U') f[3]+=f[2];
	}
	string ans="CCNU";long long mx=f[3];
	f[0]=f[1]=f[2]=f[3]=0;
	for(int i=1; i<=n; ++i){
		if(s[i]=='H') ++f[0];
		else if(s[i]=='U') f[1]+=f[0];
		else if(s[i]=='S') f[2]+=f[1];
		else if(s[i]=='T') f[3]+=f[2];
	}
	if(f[3]>mx) mx=f[3], ans="HUST";
	f[0]=f[1]=f[2]=f[3]=0;
	for(int i=1; i<=n; ++i){
		if(s[i]=='H') ++f[0];
		else if(s[i]=='Z') f[1]+=f[0];
		else if(s[i]=='A') f[2]+=f[1];
		else if(s[i]=='U') f[3]+=f[2];
	}
	if(f[3]>mx) mx=f[3], ans="HZAU";
	f[0]=f[1]=f[2]=f[3]=0;
	for(int i=1; i<=n; ++i){
		if(s[i]=='W') ++f[0];
		else if(s[i]=='H') f[1]+=f[0];
		else if(s[i]=='U') f[2]+=f[1];
	}
	if(f[2]>mx) mx=f[2], ans="WHU";
	f[0]=f[1]=f[2]=f[3]=0;
	for(int i=1; i<=n; ++i){
		if(s[i]=='W') ++f[0];
		else if(s[i]=='H') f[1]+=f[0];
		else if(s[i]=='U') f[2]+=f[1];
		else if(s[i]=='T') f[3]+=f[2];
	}
	if(f[3]>mx) mx=f[3], ans="HZAU";
	cout<<ans<<' '<<mx;
	return 0;
}

WHUT 打成 HZAU 都能过,不尊重了

L

按题意输出

Code
#include <bits/stdc++.h>
using namespace std;
signed main(){
	long long a, b, c;
	scanf("%lld%lld%lld", &a, &b, &c);
	printf("The paper you submitted have %lld Pros and %lld Cons, so I have %lld Questions for you.",
	a, b, c);
	return 0;
}

C

偷到手刹了

若在 \(l\) 之前经过,\(\operatorname{map}\) 统计最早经过时间。

若在 \(r\) 之后经过,则加上 \([l, r]\) 的差分量,此时另一个 \(\operatorname{map}\) 统计最晚经过时间。

Code
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int n, q, Dx[N], Dy[N];char s[N];
map < pair<int, int>, int > MN, MX;
signed main(){
	scanf("%d%d", &n, &q);
	scanf("%s", s+2);
	int dx=0, dy=0;
	MN[{dx, dy}]=1, MX[{dx, dy}]=1;
	for(int i=2; i<=n+1; ++i){
		if(s[i]=='L') --dx;
		if(s[i]=='R') ++dx;
		if(s[i]=='U') ++dy;
		if(s[i]=='D') --dy;
		Dx[i]=dx, Dy[i]=dy;
		if(!MN[{dx, dy}]) MN[{dx, dy}]=i;
		MX[{dx, dy}]=i; 
	}
	for(int i=1; i<=q; ++i){
		int l, r, x, y;
		scanf("%d%d%d%d", &l, &r, &x, &y);++l, ++r;
		if(MN[{x, y}]&&MN[{x, y}]<l){puts("YES");continue;}
		x+=Dx[r]-Dx[l-1], y+=Dy[r]-Dy[l-1];
		if(MX[{x, y}]>r){puts("YES");continue;}
		puts("NO");
	}
	return 0;
}

E

\(u\) 不是 \(v\) 的祖先,答案即为 \(n-siz_u\)

\(u\)\(v\) 的祖先,则找 \(v\to u\)\(u\) 的儿子

倍增/树剖都能做,长剖求个 \(k\) 级祖先应该可以线性

Code
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=3e5+5;
int n, q, siz[N], son[N], top[N], fa[N], dep[N];
vector <int> G[N];
void dfs(int x){
	siz[x]=1;
	for(auto v:G[x]) if(!dep[v]){
		dep[v]=dep[x]+1, fa[v]=x, dfs(v), siz[x]+=siz[v];
		if(siz[son[x]]<siz[v]) son[x]=v;
	} 
}
void dfs(int x, int rt){
	top[x]=rt;if(son[x]) dfs(son[x], rt);
	for(auto v:G[x]) if(!top[v]) dfs(v, v);
}
int LCA(int x, int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]>dep[top[y]]) x=fa[top[x]];
		else y=fa[top[y]];
	}return dep[x]<dep[y]?x:y;
}
signed main(){
	scanf("%d", &n);
	for(int i=1; i<n; ++i){
		int u, v;scanf("%d%d", &u, &v);
		G[u].pb(v), G[v].pb(u);
	}
	dfs(dep[1]=1);dfs(1, 1);
	scanf("%d", &q);
	for(int i=1; i<=q; ++i){
		int u, v;scanf("%d%d", &v, &u);
		int lca=LCA(u, v);
		if(lca!=v) printf("%d\n", n-siz[v]);
		else{
			int lst=0;
			while(top[u]!=top[v]) lst=top[u], u=fa[top[u]];
			if(dep[lst]==dep[v]+1) printf("%d\n", siz[lst]);
			else printf("%d\n", siz[son[v]]);
		}
	}
	return 0;
}

F

预处理每个 \(n\) 是否可行,暴力枚举 \(a, b\) 即可,复杂度 \(O(\sqrt{n}^2)=O(n)\)

Code
#include <bits/stdc++.h>
#define int long long
#define vi vector<int>
#define pb push_back
#define mp make_pair
#define st first
#define nd second
using namespace std;
typedef long long ll;
typedef pair <int, int> Pii;
const int INF=0x3f3f3f3f;
const int cp=998244353;
inline int mod(int x){return x+(x<0?cp:0)-(x>=cp?cp:0);}
inline void plust(int &x, int y){x=mod(x+y);return ;}
inline void minut(int &x, int y){x=mod(x-y);return ;}
inline int read(){
	char ch=getchar();int x=0, f=1;
	while(!isdigit(ch)){if(ch=='-') f=-1; ch=getchar();}
	while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	return x*f;
}
inline void write(int x){
    if(x<0) putchar('-'), x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
inline int ksm(int a, int b=cp-2){
	int ret=1;
	for(; b; b>>=1, a=1ll*a*a%cp)
		if(b&1) ret=1ll*ret*a%cp;
	return ret;
}
const int N=2e7+5;
bool can[N];
signed main(){
	for(int i=1; i*i<N; ++i)
		for(int j=i; i*i+j*j<N; ++j)
			can[i*i+j*j]=1;
	int q=read();while(q--) puts(can[read()]?"Yes":"No");
	return 0;
}

G

考虑前 \(2^{N-1}\) 次一直给 1,然后反复横跳 01010101 即可做到每次都错。

Code
#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
inline int read(){
	char ch=getchar();int x=0;
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0', ch=getchar();
	return x;
}
signed main(){
	int N=read(), len=read();
	int bit=1;
	for(int i=1; i<N; ++i){
		bit<<=1;
		if(bit>len) break;
	}
	if(bit>len){
		for(int i=1; i<=len; ++i) putchar('1');
		return 0;
	}
	for(int i=1; i<=bit; ++i) putchar('1');
	for(int i=bit+1; i<=len; ++i){
		if((i-bit-1)&1) putchar('1');
		else putchar('0');
	}
	return 0;
}

B

取偶数行偶数列为高地,高地显然要大的数,所以倒序把 \(n\dots 1\) 赋值,然后考虑高地旁边的数,也是倒序赋值,剩下的按顺序赋值,容易保证剩下的数不为高地

Code
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
inline int read(){
	char ch=getchar();int x=0;
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0', ch=getchar();
	return x;
}
bool is[105][105];
int a[105][105];
void slv(int n, int m, int k){
	for(int i=0; i<=n+1; ++i)
		for(int j=0; j<=m+1; ++j)
			is[i][j]=0, a[i][j]=0;
	if(n<=2||m<=2){
		if(k==0){
			puts("Yes");
			for(int i=1; i<=n; ++i, puts(""))
				for(int j=1; j<=m; ++j)
					printf("%d ", (i-1)*m+j);
		}
		else{
			puts("No"); 
		}
		return ;
	}
	int cnt=0;
	for(int i=2; i<n; i+=2)
		for(int j=2; j<m; j+=2)
			if(cnt<k) ++cnt, a[i][j]=n*m-cnt+1, is[i][j]=1;
	if(cnt<k){
		puts("No");
		return ;
	}
	for(int i=1; i<=n; ++i)
		for(int j=1; j<=m; ++j)
			if(!a[i][j])
				if(is[i-1][j]||is[i][j-1]||is[i+1][j]||is[i][j+1]||
				is[i-1][j-1]||is[i-1][j+1]||is[i+1][j-1]||is[i+1][j+1]){
					++cnt, a[i][j]=n*m-cnt+1;
				}
	for(int i=1; i<=n; ++i)
		for(int j=1; j<=m; ++j)
			if(!a[i][j]) ++cnt, a[i][j]=n*m-cnt+1;
		puts("Yes");
	for(int i=1; i<=n; ++i, puts(""))
		for(int j=1; j<=m; ++j) printf("%d ", a[i][j]);
//	int res=0;
//	for(int i=2; i<n; ++i)
//		for(int j=2; j<m; ++j)
//			if(a[i][j]){
//				int mx=0;
//				for(int dx=-1; dx<=1; ++dx)
//					for(int dy=-1; dy<=1; ++dy)
//						if(dx||dy) mx=max(mx, a[i+dx][j+dy]);
//				if(a[i][j]>mx) ++res;
//			} 
//	if(res!=k) printf("!!!!!!!!!! %d %d %d\n", n, m, k), system("pause");
}
signed main(){
	int T=read();while(T--){
		int n=read(), m=read(), k=read();
		slv(n, m, k);
	}
//	for(int i=1; i<=100; ++i)
//		for(int j=1; j<=100; ++j)
//			for(int k=0; k<=i*j; ++k)
//				slv(i, j, k);
	return 0;
}

H

\[ans=\frac{\sum_{j=0}^{\min(k, m)}C_{k}^j C_{n-k}^{m-j}(k-j)}{\sum_{j=0}^{\min(k, m)}C_{k}^j C_{n-k}^{m-j}} \]

Code
#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
const int cp=998244353;
inline int read(){
	char ch=getchar();int x=0;
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0', ch=getchar();
	return x;
}
const int N=1e6+6;
int fac[N], inv[N];
inline int C(int x, int y){
	if(x<y||y<0||x<0) return 0;
	return fac[x]*inv[y]%cp*inv[x-y]%cp;
}
inline int ksm(int a, int b=cp-2){
	int res=1;
	while(b){
		if(b&1) res=res*a%cp;
		b>>=1, a=a*a%cp;
	}return res;
}
signed main(){
	int n=read(), k=read(), m=read(), q=read();
	for(int i=fac[0]=1; i<=n; ++i) fac[i]=fac[i-1]*i%cp;
	inv[n]=ksm(fac[n]);
	for(int i=n; i>=1; --i) inv[i-1]=inv[i]*i%cp;
	int ans=0, fm=0;//printf("%lld\n", C(5, 3));
	for(int j=0; j<=min(k, m); ++j){
		int res=C(k, j)*C(n-k, m-j)%cp;
		if(res!=0) fm=(fm+res)%cp;
		ans=(ans+res*(k-j)%cp)%cp;
	}
//	printf("%lld %lld\n", ans, fm);
	ans=ans*ksm(fm)%cp;
	printf("%lld\n", ans);
	return 0;
}

M

容易写出 \(O(n^2)\) DP:

\[f_{i, j}=\max\{f_{k, j-1}+\gcd(a_{k+1}, \dots a_i)\} \]

考虑答案 \(\frac{f_{n, j}}{j}\),二分之,变成分段的惩罚

\[\frac{f_{n, j}}{j}\ge mid\to f_{n, j}-j\times mid\ge 0 \]

\[g_{i}=-mid+\max\{g_{k}+\gcd(a_{k+1}, \dots a_i)\} \]

注意到 \(\gcd(a_{k+1}, \dots a_i)\) 变化时,值至少 \(/2\),对于一个 \(i\) 最多有 \(O(\log v)\) 段不同的 \(\gcd(a_{k+1}, \dots a_i)\),每段若能求得最小的 \(g\) 即可快速 \(O(\log v)\) 转移。

那么考虑从 \(i-1\) 的后缀 \(\gcd\) 变为 \(i\) 的后缀 \(\gcd\),每个对 \(a_i\) 取完 \(\gcd\) 后合并连续相同的,区间 \(\min g\) 也能在此时实现。

直接模拟的话 check\(O(n\log^2 v)\) 的,多的一个 $\log $ 瓶颈在于每次对 \(a_i\)\(\gcd\),注意到 \(mid\) 不影响这些 \(\gcd\),提前预处理好即可。

Code
#include <bits/stdc++.h>
#define vi vector<int>
#define pb push_back
#define mp make_pair
#define st first
#define nd second
using namespace std;
typedef long long ll;
typedef pair <int, int> Pii;
const int INF=0x3f3f3f3f;
const int cp=998244353;
inline int mod(int x){return x+(x<0?cp:0)-(x>=cp?cp:0);}
inline void plust(int &x, int y){x=mod(x+y);return ;}
inline void minut(int &x, int y){x=mod(x-y);return ;}
inline int read(){
	char ch=getchar();int x=0, f=1;
	while(!isdigit(ch)){if(ch=='-') f=-1; ch=getchar();}
	while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	return x*f;
}
inline void write(int x){
    if(x<0) putchar('-'), x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
inline int ksm(int a, int b=cp-2){
	int ret=1;
	for(; b; b>>=1, a=1ll*a*a%cp)
		if(b&1) ret=1ll*ret*a%cp;
	return ret;
}
const int N=2e5+5;
const double inf=-1e6;
int gcd(int x, int y){return y?gcd(y, x%y):x;}
vector < pair<int, Pii> >F[N]; 
vector < pair<int, double> > G[N];
double f[N];int n, a[N];
bool check(double k){
	G[1].clear();G[1].pb({a[1], 0});
	f[1]=a[1]-k;
	for(int i=2; i<=n; ++i){
		int p=0;
		for(auto v:F[i]){
			int l=v.nd.st, r=v.nd.nd, lp=min(r, (int)F[i-1].size()-1);
			double mxf=inf;
			for(int j=l; j<=lp; ++j) mxf=max(mxf, G[i-1][j].nd);
			if(lp!=r) mxf=max(mxf, f[i-1]);
			G[i][p++]={v.st, mxf};
		}
		f[i]=inf;
		for(auto v:G[i]) f[i]=max(f[i], v.nd+v.st-k);
	}
	return f[n]>=0;
}
void slv(){
	n=read();a[1]=read();
	F[1].clear();
	F[1].pb({a[1], {0, 0}});
	for(int i=2; i<=n; ++i){
		a[i]=read();vi tmp;F[i].clear();
		for(auto v:F[i-1]) tmp.pb(gcd(a[i], v.st));tmp.pb(a[i]);
		int len=tmp.size();
		for(int l=0, r; l<len; l=r+1){
			r=l;while(r+1<len&&tmp[r+1]==tmp[l]) ++r;
			F[i].pb({tmp[l], {l, r}});
		}
		G[i].resize(F[i].size());
		// for(auto v:F[i]) printf("%d ", v.st);puts("");
	}
	double l=0, r=-inf, ans=0;
	// check(5.000);
	for(int i=0; i<=50; ++i){
		double mid=(l+r)/2.0;
		if(check(mid)) l=mid, ans=mid;
		else r=mid;
	}printf("%.12lf\n", ans);
}
signed main(){
	int T=read();while(T--) slv();
	return 0;
}

D

设源点 \(S\),汇点 \(T\),物品点叫 \(s_i\),工厂点叫 \(t_i\)

那么 \(S\) 往每个 \(s_i\) 连边,流量 \(a_i\),费用为 \(0\)

每个 \(t_i\)\(T\) 连边,流量 \(b_i\),费用为 \(0\)

每个 \(s_i\) 往每个 \(t_j\) 连边,流量 \(1\),费用为 \(w_{i, j}\)

对建立的图跑有最大费用最大流,使用Primal-Dual 原始对偶算法即可做到 \(O(n^3+(\sum b_i)n^2\log (n^4))\)

Code
#include <bits/stdc++.h>
using namespace std;
// Primal-Dual min-cost max-flow (using potentials + Dijkstra).
// We maximize total value by sending required flow with costs = -w.
struct Edge {
    int to, rev, cap;
    long long cost;
};
struct MinCostMaxFlow {
    int n, src, sink;
    vector<vector<Edge>> g;
    vector<long long> pot;   // potentials to ensure non-negative reduced costs
    vector<long long> dist;  // shortest distances in reduced graph
    vector<pair<int, int>> pre; // previous node and edge index on shortest path
    explicit MinCostMaxFlow(int n_) : n(n_), g(n_), pot(n_), dist(n_), pre(n_) {}
    void add_edge(int u, int v, int cap, long long cost) {
        Edge a{v, (int)g[v].size(), cap, cost};
        Edge b{u, (int)g[u].size(), 0, -cost};
        g[u].push_back(a);
        g[v].push_back(b);
    }
    // SPFA to get initial potentials even with negative costs.
    void init_potentials(int s) {
        const long long INF = (1LL << 60);
        fill(pot.begin(), pot.end(), INF);
        vector<int> inq(n, 0);
        queue<int> q;
        pot[s] = 0;
        q.push(s);
        inq[s] = 1;
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            inq[u] = 0;
            for (const auto &e : g[u]) {
                if (e.cap && pot[u] + e.cost < pot[e.to]) {
                    pot[e.to] = pot[u] + e.cost;
                    if (!inq[e.to]) {
                        inq[e.to] = 1;
                        q.push(e.to);
                    }
                }
            }
        }
        for (auto &v : pot) if (v == INF) v = 0; // unreachable nodes keep zero potential
    }
    pair<int, long long> max_cost_flow(int s, int t, int need) {
        src = s;
        sink = t;
        const long long INF = (1LL << 60);
        init_potentials(s);
        int flow = 0;
        long long cost = 0;
        while (flow < need) {
            fill(dist.begin(), dist.end(), INF);
            vector<int> inq(n, 0);
            priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq;
            dist[s] = 0;
            pq.push({0, s});
            while (!pq.empty()) {
                auto [d, u] = pq.top();
                pq.pop();
                if (d != dist[u]) continue;
                for (int i = 0; i < (int)g[u].size(); ++i) {
                    const auto &e = g[u][i];
                    if (!e.cap) continue;
                    long long w = e.cost + pot[u] - pot[e.to];
                    if (dist[u] + w < dist[e.to]) {
                        dist[e.to] = dist[u] + w;
                        pre[e.to] = {u, i};
                        pq.push({dist[e.to], e.to});
                    }
                }
            }
            if (dist[t] == INF) break; // no more augmenting path
            for (int v = 0; v < n; ++v) if (dist[v] < INF) pot[v] += dist[v];
            // augment 1 unit along shortest path (all item-factory edges have cap 1)
            int add = need - flow;
            int v = t;
            while (v != s) {
                auto [u, idx] = pre[v];
                add = min(add, g[u][idx].cap);
                v = u;
            }
            v = t;
            while (v != s) {
                auto [u, idx] = pre[v];
                Edge &e = g[u][idx];
                Edge &rev = g[v][e.rev];
                e.cap -= add;
                rev.cap += add;
                cost += 1LL * add * e.cost;
                v = u;
            }
            flow += add;
        }
        return {flow, cost};
    }
};
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    if (!(cin >> T)) return 0;
    while (T--) {
        int n, m;
        cin >> n >> m;
        vector<int> a(n), b(m);
        long long need = 0;
        for (int i = 0; i < n; ++i) {
            cin >> a[i];
            need += a[i];
        }
        long long cap_sum = 0;
        for (int j = 0; j < m; ++j) {
            cin >> b[j];
            cap_sum += b[j];
        }
        vector<vector<long long>> w(n, vector<long long>(m));
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) cin >> w[i][j];
        }
        if (cap_sum < need) {
            cout << -1 << '\n';
            continue;
        }
        int N = 1 + n + m + 1; // S + items + factories + T
        int S = 0;
        int Tt = N - 1;
        MinCostMaxFlow mcmf(N);
        for (int i = 0; i < n; ++i) mcmf.add_edge(S, 1 + i, a[i], 0);
        for (int j = 0; j < m; ++j) mcmf.add_edge(1 + n + j, Tt, b[j], 0);
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                // capacity 1, cost = -w to maximize gain
                mcmf.add_edge(1 + i, 1 + n + j, 1, -w[i][j]);
            }
        }
        auto [flow, cost] = mcmf.max_cost_flow(S, Tt, (int)need);
        if (flow != need) {
            cout << -1 << '\n';
        } else {
            cout << -cost << '\n';
        }
    }
    return 0;
}

从小到大没写过流,代码拿AI跑的,擦线过时限了

然鹅将原始对偶的堆版 dij 换成 \(O(V^2+E)\) 的即可做到少 \(\log\)

Code
#include <bits/stdc++.h>
using namespace std;
// Primal-Dual min-cost max-flow using adjacency matrices and O(V^2+E) Dijkstra (no heap).
struct MinCostMaxFlow {
    int n;
    vector<vector<int>> cap;          // residual capacities
    vector<vector<long long>> cost;   // edge costs
    vector<long long> pot;            // potentials
    vector<long long> dist;           // distances in reduced graph
    vector<int> pre;                  // previous node on shortest path
    explicit MinCostMaxFlow(int n_) : n(n_), cap(n_, vector<int>(n_, 0)), cost(n_, vector<long long>(n_, 0)), pot(n_), dist(n_), pre(n_) {}
    void add_edge(int u, int v, int c, long long w) {
        cap[u][v] = c;
        cap[v][u] = 0;
        cost[u][v] = w;
        cost[v][u] = -w;
    }
    // SPFA to initialize potentials when negative edges exist.
    void init_potentials(int s) {
        const long long INF = (1LL << 60);
        fill(pot.begin(), pot.end(), INF);
        vector<char> inq(n, 0);
        queue<int> q;
        pot[s] = 0;
        q.push(s);
        inq[s] = 1;
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            inq[u] = 0;
            for (int v = 0; v < n; ++v) {
                if (cap[u][v] == 0) continue;
                long long nd = pot[u] + cost[u][v];
                if (nd < pot[v]) {
                    pot[v] = nd;
                    if (!inq[v]) { inq[v] = 1; q.push(v); }
                }
            }
        }
        for (auto &v : pot) if (v == INF) v = 0; // unreachable nodes keep zero potential
    }
    pair<int, long long> max_cost_flow(int s, int t, int need) {
        const long long INF = (1LL << 60);
        init_potentials(s);
        int flow = 0;
        long long cost_sum = 0;
        while (flow < need) {
            fill(dist.begin(), dist.end(), INF);
            vector<char> vis(n, 0);
            dist[s] = 0;
            for (int iter = 0; iter < n; ++iter) {
                int u = -1;
                long long best = INF;
                for (int v = 0; v < n; ++v) if (!vis[v] && dist[v] < best) { best = dist[v]; u = v; }
                if (u == -1 || best == INF) break;
                vis[u] = 1;
                for (int v = 0; v < n; ++v) if (cap[u][v] > 0) {
                    long long w = cost[u][v] + pot[u] - pot[v];
                    if (dist[u] + w < dist[v]) {
                        dist[v] = dist[u] + w;
                        pre[v] = u;
                    }
                }
            }
            if (dist[t] == INF) break; // no more augmenting path
            for (int v = 0; v < n; ++v) if (dist[v] < INF) pot[v] += dist[v];
            int add = need - flow;
            int v = t;
            while (v != s) {
                int u = pre[v];
                add = min(add, cap[u][v]);
                v = u;
            }
            v = t;
            while (v != s) {
                int u = pre[v];
                cap[u][v] -= add;
                cap[v][u] += add;
                cost_sum += 1LL * add * cost[u][v];
                v = u;
            }
            flow += add;
        }
        return {flow, cost_sum};
    }
};
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    if (!(cin >> T)) return 0;
    while (T--) {
        int n, m;
        cin >> n >> m;
        vector<int> a(n), b(m);
        long long need = 0;
        for (int i = 0; i < n; ++i) {
            cin >> a[i];
            need += a[i];
        }
        long long cap_sum = 0;
        for (int j = 0; j < m; ++j) {
            cin >> b[j];
            cap_sum += b[j];
        }
        vector<vector<long long>> w(n, vector<long long>(m));
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) cin >> w[i][j];
        }
        if (cap_sum < need) {
            cout << -1 << '\n';
            continue;
        }
        int N = 1 + n + m + 1; // S + items + factories + T
        int S = 0;
        int Tt = N - 1;
        MinCostMaxFlow mcmf(N);
        // S -> s_i (capacity a[i], cost 0)
        for (int i = 0; i < n; ++i) mcmf.add_edge(S, 1 + i, a[i], 0);
        // t_j -> T (capacity b[j], cost 0)
        for (int j = 0; j < m; ++j) mcmf.add_edge(1 + n + j, Tt, b[j], 0);
        // s_i -> t_j (capacity 1, cost = -w[i][j] to maximize value)
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                mcmf.add_edge(1 + i, 1 + n + j, 1, -w[i][j]);
            }
        }
        auto [flow, cost] = mcmf.max_cost_flow(S, Tt, (int)need);
        if (flow != need) {
            cout << -1 << '\n';
        } else {
            cout << -cost << '\n';
        }
    }
    return 0;
}

J

设询问模式二进制串 \(S_i\)\(bit_s\)\(s\)1 的个数,则所求

\[\max_{T} f(T), f(T)=\sum a_{bit_{S_{i}\& T}} \]

容易高维前缀和统计超集和 \(cnt_t=\sum [t \sube S_i]\),考虑按位容斥

我们考虑每个 \(bit_s \le n, s\in S_i\)\(s\),我们希望它能合法贡献到 \(f(T)\),显然 \(s\sube S_i\& T\to s\sube T\),考虑构造子集容斥系数。

对于 \(0=s_0\subset s_1\subset s_2 \subset s_3 \subset \dots s_{m}=S_i\& T\)(其中下标表示 1 的个数,\(m\le bit_T\)),我们不希望 \(s_{1-(m-1)}\) 有贡献,

考虑二项式容斥,待定容斥系数 \(q_i\),我们希望

\[g(S_k\& T)=\sum_{i=0}^{m} \binom{m}{i}q_i=a_m \]

显然有二项式反演的形式,令

\[q_i=\sum_{j=0}^{i} (-1)^{i-j}\binom{i}{j} a_i \]

即可。

实际上,在计算 \(g\) 的过程中,它是分散在 \(cnt_t\) 里的,于是对 \(\phi(t)=cnt_t\times q_{bit_t}\) 做高维前缀和统计子集和得到 \(f(T)\),枚举所有情况比较即可

Code
#include <bits/stdc++.h>
#define int long long
#define int long long
#define vi vector<int>
#define pb push_back
#define mp make_pair
#define st first
#define nd second
using namespace std;
typedef long long ll;
typedef pair <int, int> Pii;
const int INF=0x3f3f3f3f;
const int cp=998244353;
inline int mod(int x){return x+(x<0?cp:0)-(x>=cp?cp:0);}
inline void plust(int &x, int y){x=mod(x+y);return ;}
inline void minut(int &x, int y){x=mod(x-y);return ;}
inline int read(){
	char ch=getchar();int x=0, f=1;
	while(!isdigit(ch)){if(ch=='-') f=-1; ch=getchar();}
	while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	return x*f;
}
inline void write(int x){
    if(x<0) putchar('-'), x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
inline int ksm(int a, int b=cp-2){
	int ret=1;
	for(; b; b>>=1, a=1ll*a*a%cp)
		if(b&1) ret=1ll*ret*a%cp;
	return ret;
}
signed main(){
	int C[24][24]={};
	for(int i=0; i<=23; ++i)
		for(int j=C[i][0]=C[i][i]=1; j<i; ++j) C[i][j]=C[i-1][j-1]+C[i-1][j];
	int n=read(), m=read(), k=read(), M=1<<m;
	vi a(n+1, 0);for(int i=1; i<=n; ++i) a[i]=read();
	vi cnt(M, 0);
	for(int i=1; i<=k; ++i){
		int s=0;for(int j=1; j<=n; ++j) s|=1<<(read()-1);
		++cnt[s];
	}
	for(int i=0, t=1; i<m; ++i, t<<=1)
		for(int s=0; s<M; ++s)
			if(!(s&t)) cnt[s]+=cnt[s|t];
	vi f(n+1, 0), g(M, 0);
	for(int i=0; i<=n; ++i)
		for(int j=0; j<=i; ++j)
			f[i]+=(((i-j)&1)?-1ll:1ll)*C[i][j]*a[j];
	for(int s=0; s<M; ++s){
		int bp=__builtin_popcount(static_cast<unsigned>(s));
		if(bp<=n) g[s]=f[bp]*cnt[s];
	}
	for(int i=0, t=1; i<m; ++i, t<<=1)
		for(int s=0; s<M; ++s)
			if(s&t) g[s]+=g[s^t];
	int ans=LLONG_MAX, S=0;
	for(int s=0; s<M; ++s){
		if(__builtin_popcount(static_cast<unsigned>(s))!=n) continue;
		int c=g[s];if(c<ans||(c==ans&&s<S)) ans=c, S=s;
	}
	printf("%lld\n", ans);
	for(int i=m-1, t=1<<m-1; i>=0; --i, t>>=1) if(S&t) printf("%lld ", i+1);
	return 0;
}

K

首先会比较两个子串 \([l, r], [x, y]\),考虑主席树维护值域 \(cnt\) 哈希值,二分即可。

显然有

\[\operatorname{rank}(l, r)<\operatorname{rank}(l+d, r) \]

\[\operatorname{rank}(l, r)<\operatorname{rank}(l, r-d) \]

容易证明前 \(k\) 小形成一个三角形的结构。

考虑类似快排的“二分”,rand 一个三角形内部的子串 \(mid\),查这个子串的 \(\operatorname{rank}\),由于边界单调,很容易做到指针扫一遍求得 \(<mid\)\(\le mid\) 的串的个数。

实际二分时无法做到在不调整左边界的时候去求下一个 \(mid\),于是我们不维护三角形状物,改为直接维护答案区间(相当于维护所有合法位置),我选取 \(mid\) 的策略是在最长部分取中间点。

复杂度 \(O(cn\log n)\)\(c\) 为二分次数,期望是 \(O(\log n^2)\)

在剩余部分过小时,为了保证正确性,可以直接排序,或者做 \(n\) 路归并

Code
#include <bits/stdc++.h>
#define vi vector<int>
#define pb push_back
#define mp make_pair
#define st first
#define nd second
using namespace std;
typedef long long ll;
typedef pair <int, int> Pii;
const int INF=0x3f3f3f3f;
const int cp=998244353;
inline int mod(int x){return x+(x<0?cp:0)-(x>=cp?cp:0);}
inline void plust(int &x, int y){x=mod(x+y);return ;}
inline void minut(int &x, int y){x=mod(x-y);return ;}
inline int read(){
	char ch=getchar();int x=0, f=1;
	while(!isdigit(ch)){if(ch=='-') f=-1; ch=getchar();}
	while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	return x*f;
}
inline void write(int x){
    if(x<0) putchar('-'), x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
inline int ksm(int a, int b=cp-2){
	int ret=1;
	for(; b; b>>=1, a=1ll*a*a%cp)
		if(b&1) ret=1ll*ret*a%cp;
	return ret;
}
const int N=1e5+5;
int rt[N];
namespace Tree{
    struct node{int lc, rc, sum, h1, h2;}tr[N*20];
    static constexpr int MOD1 = 1000000007, MOD2 = 1000000009;
    static constexpr int BASE1 = 911382323, BASE2 = 972663749;
    int pw1[N], pw2[N], ndc, n;
    void init(int _n){
    	n=_n;
        for(int i=pw1[0]=pw2[0]=1; i<=n; ++i) 
            pw1[i]=1LL*pw1[i-1]*BASE1%MOD1,
            pw2[i]=1LL*pw2[i-1]*BASE2%MOD2;
    }
    void pushup(int k){
        int ls=tr[k].lc, rs=tr[k].rc;
        tr[k].sum=tr[ls].sum+tr[rs].sum;
        tr[k].h1=tr[ls].h1+tr[rs].h1;if(tr[k].h1>=MOD1) tr[k].h1-=MOD1;
        tr[k].h2=tr[ls].h2+tr[rs].h2;if(tr[k].h2>=MOD2) tr[k].h2-=MOD2;
    }
    void update(int &k, int pre, int l, int r, int x){
        tr[k=++ndc]=tr[pre];int mid=l+r>>1;
        if(l==r){
            tr[k].sum=tr[pre].sum+1;
            tr[k].h1=(tr[pre].h1+pw1[x])%MOD1;
            tr[k].h2=(tr[pre].h2+pw2[x])%MOD2;
            return ;
        }
        if(x<=mid) update(tr[k].lc, tr[pre].lc, l, mid, x);
        else update(tr[k].rc, tr[pre].rc, mid+1, r, x);
        pushup(k);
    }
    bool compare(int l, int r, int x, int y){
        int aR=rt[r], aL=rt[l-1], bR=rt[y], bL=rt[x-1], L=1, R=n;
        while(L!=R){
            int aRl=tr[aR].lc, aLl=tr[aL].lc;
            int bRl=tr[bR].lc, bLl=tr[bL].lc;
            int ha1=tr[aRl].h1-tr[aLl].h1;if(ha1<0) ha1+=MOD1;
            int hb1=tr[bRl].h1-tr[bLl].h1;if(hb1<0) hb1+=MOD1;
            int ha2=tr[aRl].h2-tr[aLl].h2;if(ha2<0) ha2+=MOD2;
            int hb2=tr[bRl].h2-tr[bLl].h2;if(hb2<0) hb2+=MOD2;
            int mid=L+R>>1;
            if(ha1!=hb1||ha2!=hb2){//左子树不等
                aR=aRl; aL=aLl;
                bR=bRl; bL=bLl;
                R=mid;
            }else{
                aR=tr[aR].rc, aL=tr[aL].rc;
                bR=tr[bR].rc, bL=tr[bL].rc;
                L=mid+1;
            }
        }
        int ca=tr[aR].sum-tr[aL].sum;
        int cb=tr[bR].sum-tr[bL].sum;
        return ca>cb;
    }
}
int n, k, s[N], lp[N], rp[N], les[N], leq[N];
signed main(){
    n=read(), k=read();
    Tree::init(n);
    for(int i=1; i<=n; ++i) s[i]=read(),
        Tree::update(rt[i], rt[i-1], 1, n, s[i]);
    for(int i=1; i<=n; ++i) lp[i]=i, rp[i]=n;
    auto res=[&](){ll s=0;for(int i=1; i<=n; ++i) if(lp[i]<=rp[i]) s+=rp[i]-lp[i]+1;return s;};
    auto pick_mid=[&]()-> pair<int, int> {
        int l=-1, len=-1;
        for(int i=1; i<=n; ++i) 
            if(lp[i]<=rp[i]){
                int len2=rp[i]-lp[i]+1;
                if(len2>len) len=len2, l=i;
            }
        int r=(lp[l]+rp[l])>>1;
        return {l, r};
    };
    for(int c=0; c<200; ++c){
        ll rem=res();
        if(rem<=200000){
            struct Cmp{ 
                bool operator()(const Pii& a, const Pii& b) const{
                    return Tree::compare(b.st, b.nd, a.st, a.nd);//大根堆
                }
            };
            priority_queue<Pii, vector<Pii>, Cmp> Q;
            for(int i=1; i<=n; ++i) 
                if(lp[i]<=rp[i]) Q.push({i, rp[i]});
            Pii ans=Q.top();
            while(k-->0){
                ans=Q.top(); Q.pop();
                if(ans.nd-1>=lp[ans.st]) Q.push({ans.st, ans.nd-1});
            }
            vector<int> out;
            for(int i=ans.st; i<=ans.nd; ++i) out.pb(s[i]);
            sort(out.begin(), out.end());
            for(auto v: out) write(v), putchar(' ');puts("");
            return 0;
        }
        else{
            auto mid=pick_mid();
            int pl=mid.st, pr=mid.nd;
            ll cntles=0, cntleq=0;//les < leq <=
            for(int i=1, re=1, rq=1; i<=n; ++i){
                if(lp[i]>rp[i]){les[i]=leq[i]=rp[i]+1;continue;}
                if(re<lp[i]) re=lp[i];if(rq<lp[i]) rq=lp[i];
                while(re<=rp[i]&& Tree::compare(pl, pr, i, re)) ++re;les[i]=re;
                while(rq<=rp[i]&&!Tree::compare(i, rq, pl, pr)) ++rq;leq[i]=rq;
                cntles+=max(0, rp[i]-les[i]+1);
                cntleq+=max(0, rp[i]-leq[i]+1);
            }
            if(k<=cntles){
                for(int i=1; i<=n; ++i) 
                    if(lp[i]<=rp[i]) lp[i]=max(lp[i], les[i]);
            }
            else if(k>cntleq){
                k-=cntleq;
                for(int i=1; i<=n; ++i) 
                    if(lp[i]<=rp[i]) rp[i]=min(rp[i], leq[i]-1);
            }
            else{
                vector<int> out;
                for(int i=pl; i<=pr; ++i) out.pb(s[i]);
                sort(out.begin(), out.end());
                for(auto v: out) write(v), putchar(' ');puts("");
                return 0;
            }
        }
    }
	return 0;
}

更进一步地,你发现可以用 \(O(n)\) 的代价处理 \(\operatorname{rank}(l, r)\)\(\operatorname{rank}(x, y)\) 的比较数组,那么考虑 check 时先暴力跑出第一列的变化,根据单调性,后面每一列的变化可以均摊 \(O(n)\) 求出,省下了主席树比较的复杂度,可以做到 \(O(cn)\),抽象点说就是发现下面这部分代码移动时端点变化长度为 1,其实很容易 \(O(1)\) 维护比较结果。

check
for(int i=1, re=1, rq=1; i<=n; ++i){
	if(lp[i]>rp[i]){les[i]=leq[i]=rp[i]+1;continue;}
	if(re<lp[i]) re=lp[i];if(rq<lp[i]) rq=lp[i];
	while(re<=rp[i]&& Tree::compare(pl, pr, i, re)) ++re;les[i]=re;
	while(rq<=rp[i]&&!Tree::compare(i, rq, pl, pr)) ++rq;leq[i]=rq;
	cntles+=max(0, rp[i]-les[i]+1);
	cntleq+=max(0, rp[i]-leq[i]+1);
}

I

考虑如何求 \(\operatorname{LIS}\),设 \(f_{i, j}\) 为前 \(i\) 个元素中,以 \(a_x=j\) 结尾的 \(\operatorname{LIS}\) 的最长长度,显然 \(f_{i}\) 递增。

dfs 每个可能的 \(DP\) 数组,具体地,我们维护最终答案和 \(g_{j}=\max_{k<j} f_{i-1, j}\)

  1. 当我们不选第 \(i\) 个数,之前的状态不用改变。
  2. 当我们选第 \(i\) 个数,那么 \(\operatorname{LIS}\) 若在这个数停止,我们用 \(g_{a_i}+1\) 更新答案,若 \(a_i\) 要做中间元素,则更新 \(>a_i\) 的所有 \(g\)

由于是排列,所以 在 2 这里,\(a_i\) 这个数已经不会在后面再对最终答案有贡献,所以 “更新 \(>a_i\) 的所有 \(g\)”这里不对之前所有 \(a_x\) 更新。

用 哈希和unordered_map存储状态。

状态数十分玄学,伟大的 啊爱 说是 \(O(n^2p(n))\) 的,\(p\) 是自然数分拆数。

Code
#include <bits/stdc++.h>
#define vi vector<int>
#define pb push_back
#define mp make_pair
#define st first
#define nd second
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef uint8_t u8;
typedef pair <int, int> Pii;
const int INF=0x3f3f3f3f;
const int cp=998244353;
inline int mod(int x){return x+(x<0?cp:0)-(x>=cp?cp:0);}
inline void plust(int &x, int y){x=mod(x+y);return ;}
inline void minut(int &x, int y){x=mod(x-y);return ;}
inline int read(){
	char ch=getchar();int x=0, f=1;
	while(!isdigit(ch)){if(ch=='-') f=-1; ch=getchar();}
	while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	return x*f;
}
inline void write(int x){
    if(x<0) putchar('-'), x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
inline int ksm(int a, int b=cp-2){
	int ret=1;
	for(; b; b>>=1, a=1ll*a*a%cp)
		if(b&1) ret=1ll*ret*a%cp;
	return ret;
}
inline ull random(ull x){
	x+=0x9e3779b97f4a7c15ULL;
	x=(x^(x>>30))*0xbf58476d1ce4e5b9ULL;
	x=(x^(x>>27))*0x94d049bb133111ebULL;
	return x^(x>>31);
}
ull tg[36];
inline void init_tag(){
	ull seed=0x123456789abcdef0ULL;
	for(int i=0;i<=35;++i) tg[i]=random(seed+(ull)i*1315423911ULL);
}
struct State{
	char a[36];ull h, cnt;
	inline void chg(int i, char to){h+=(ull)((int)to-(int)a[i])*tg[i];a[i]=to;}
}t;
bool vis[36];
signed main(){
	init_tag();
	int n=read();vi a(n), id(n);
	for(int i=0; i<n; ++i) a[id[i]=i]=read();
	sort(id.begin(), id.end(), [&](int x, int y){return a[x]<a[y];});
	for(int i=0; i<n; ++i) a[id[i]]=i+1;
	unordered_map<ull, State> cur, nxt;
	t.cnt=1;
	cur.emplace(0, t);
	for(int i=0; i<n; ++i){
		u8 x=(u8)a[i];
		nxt.clear();
		nxt.reserve(cur.size()*2+8);
		for(auto &it:cur){
			State u=it.nd;
			//不选
			State t=u;
			t.chg(x, 0);
			auto it2=nxt.find(t.h);
			if(it2==nxt.end()) nxt.emplace(t.h, t);
			else it2->nd.cnt+=t.cnt;
			//选
			t=u;
			char curmx=t.a[x]+1;
			if(t.a[0]<curmx) t.chg(0, curmx);
			for(int v=x+1; v<=n; ++v) 
				if(!vis[v]&&t.a[v]<curmx) t.chg(v, curmx);
			t.chg(x, 0);
			it2=nxt.find(t.h);
			if(it2==nxt.end()) nxt.emplace(t.h, t);
			else it2->nd.cnt+=t.cnt;
		}
		vis[x]=1;
		cur.swap(nxt);
	}
	vector<ull> ans(n+1, 0);
	for(auto &it:cur) ans[(int)it.nd.a[0]]+=it.nd.cnt;
	for(int k=0; k<=n; ++k) printf("%llu\n", ans[k]);
	return 0;
}
posted @ 2025-12-20 23:36  127_127_127  阅读(42)  评论(0)    收藏  举报