2022NOIP A层联测31

A. 不稳定的道路

基本不等式

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll, int> pli;

int read(){
    int x = 0; char c = getchar();
    while(!isdigit(c))c = getchar();
    do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
    return x;
}

const int maxn = 100005;
struct edge{int to, net, c, d;}e[maxn << 1 | 1];
int head[maxn], tot;
void add(int u, int v, int c, int d){
	e[++tot].net = head[u];
	head[u] = tot;
	e[tot].to = v;
	e[tot].c = c;
	e[tot].d = d;
}

ll dis[maxn];
priority_queue<pli, vector<pli>, greater<pli>>q;
bool vis[maxn];
int n, m;
int main(){
    freopen("road.in","r",stdin);
    freopen("road.out","w",stdout);
	n = read(), m = read();
	for(int i = 1; i <= m; ++i){
		int u = read(), v = read(), c = read(), d = read();
		add(u, v, c, d); add(v, u, c, d);
	}
	for(int i = 0; i <= n; ++i)dis[i] = 0x3f3f3f3f3f3f3f3f;
	dis[1] = 0; q.push(pli(0, 1));
	while(!q.empty()){
		int x = q.top().second; q.pop();
		if(vis[x])continue;
		vis[x] = true;
		for(int i = head[x]; i; i = e[i].net){
			int v = e[i].to;
			ll c = e[i].c, d = e[i].d;
			ll mi = dis[x] + c + d / (dis[x] + 1);
			ll sq = sqrt(d);
			for(ll l = max(dis[x] + 1, sq - 10), r; l <= min(sq + 10, d); l = r + 1){
				r = d / (d / l);
				mi = min(mi, l + c + d / l - 1);
			}
			mi = min(mi, max(dis[x], d) + c);
			if(dis[v] > mi){
				dis[v] = mi;
				q.push(pli(mi, v));
			}
		}
	}
	if(dis[n] == dis[0])printf("-1\n");
	else printf("%lld\n",dis[n]);
    return 0;
}

B. 翻转有向图

一直在想 tarjan 过程中的树边返祖边横叉边,然后啥也不会

考虑一条边的影响,其实无非几种情况,而且只用考虑他连接的两个点 u>v

如果他有影响,那么一定会影响他连接的两个点的关系

  1. 之前不是SCC,反向后为SCC

存在 u>v 另一条路径,不存在 v>u 路径

  1. 之前不是,之后不是

不存在 u>v 另一条路径, 不存在 vu 路径

  1. 之前是,之后不是

不存在 u>v 另一条路径, 存在 v>u 路径

  1. 之前之后都是

存在 u>v 另一条路径, 存在 v>u 路径

然后我们需要得到

u>v 是否存在多于一条路径, u>v 是否存在路径

存在路径直接从每个点出发扫就行

如何解决前面那个?

一个关键的点在于我们只关心由一条边连接的 u,v,

那么如果存在其他路径,我们把边表正序倒序,两次dfsu 出发到达 v 的边一定不是同一条

于是可以进行两次染色

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
    int x = 0; char c = getchar();
    while(!isdigit(c))c = getchar();
    do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
    return x;
}

const int maxn = 1005;

int n, m;
int u[maxn * maxn], v[maxn * maxn];
vector<int>g[maxn];
int a[maxn][maxn], b[maxn][maxn];
void dfs(int x, int tp, int *col){
	if(col[x])return;
	col[x] = tp;
	for(int v : g[x])dfs(v, tp, col);
}
int main(){
    freopen("turn.in","r",stdin);
    freopen("turn.out","w",stdout);
	n = read(), m = read();
    for(int i = 1; i <= m; ++i){
		u[i] = read(), v[i] = read();
		g[u[i]].push_back(v[i]);
	}
	for(int i = 1; i <= n; ++i){
		a[i][i] = b[i][i] = 1;
		for(int j : g[i])dfs(j, j, a[i]);
		reverse(g[i].begin(), g[i].end());
		for(int j : g[i])dfs(j, j, b[i]);
	}
	for(int i = 1; i <= m; ++i)if((a[u[i]][v[i]] != b[u[i]][v[i]]) ^ (a[v[i]][u[i]] > 0))printf("diff\n");else printf("same\n");
	return 0;
}

C. 在表格里造序列

题解说的很好

image

还有一种做法,复杂度不会分析

就是用莫比乌斯反演容斥求 fm(n)

fm(n)=d=1nμ(d)(n/d)m

整除分块

外面那个 ϕ 我考场没想到,但是也可以求

i=1nj=1nfm(...)

做法是枚举 gcd 然后莫反

带上整除分块+杜教筛求 μ 的前缀和也能做

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

ll read(){
    ll x = 0; char c = getchar();
    while(!isdigit(c))c = getchar();
    do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
    return x;
}

const int maxn = 10000055;
const int mx = 10000000;
const int mod = 998244353;
ll qpow(ll x, ll y){
	x %= mod; y %= (mod - 1);
	ll ans = 1;
	for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
	return ans;
}
ll n, m;
int prime[maxn], cnt, phi[maxn];
bool flag[maxn];
void init(int n){
	phi[1] = 1;
	for(int i = 2; i <= n; ++i){
		if(!flag[i])prime[++cnt] = i, phi[i] = i - 1;
		for(int j = 1; j <= cnt && i * prime[j] <= n; ++j){
			flag[i * prime[j]] = 1;
			if(i % prime[j] == 0){
				phi[i * prime[j]] = phi[i] * prime[j];
				break;
			}
			phi[i * prime[j]] = phi[i] * phi[prime[j]];
		}
	}
	for(int i = 2; i <= n; ++i)phi[i] = (phi[i - 1] + phi[i]) % mod;
}
int sphi(int n){
	if(n <= mx)return phi[n];
	int ans = 1ll * n * (n + 1) / 2 % mod;
	for(int l = 2, r; l <= n; l = r + 1){
		r = n / (n / l);
		ans = (ans - 1ll * (r - l + 1) * sphi(n / l) % mod + mod) % mod;
	}
	return ans;
}
int f[maxn];
int F(int n){
	if(n <= mx && f[n])return f[n];
	int ans = qpow(n, m);
	for(int l = 2, r; l <= n; l = r + 1){
		r = n / (n / l);
		ans = (ans - 1ll * (r - l + 1) * F(n / l) % mod + mod) % mod;
	}
	if(n <= mx)f[n] = ans;
	return ans;
}
int main(){
    freopen("table.in","r",stdin);
    freopen("table.out","w",stdout);
   	n = read(), m = read();
	init(mx);
	int ans = 0;
	for(int l = 1, r; l <= n; l = r + 1){
		r = n / (n / l); int sp = sphi(n / l);
		ans = (ans + 1ll * F(n / l) * (sp + sp - 1) % mod * (r - l + 1) % mod) % mod;
	}
	printf("%d\n",ans);
	return 0;
}

D. zzzyyds

学习的 lyin 写法,只能说 lyin 太巨了,一个概率题,做成了计数题

显然的一点是考虑连续段 DP, 但是我一直纠结于如何去掉不合法的状态,然而事实上这东西根本不用考虑,取最终合法的答案即可,不懂继续看

gi,j 为有 i 天假日, 他们组成了 j 个连续段的概率

fi,j 表示期望

因为我们用类似计数的方法来做,所以在最终确定答案之前,所有状态的概率和不一定为 1

转移考虑对连续段的影响

每次转移,有 1ni 的概率选择到一个工作日

因为有 j 个连续段,在每个连续段后操作对应不同的方案,一共有 j 种方案

于是转移概率为 x=gi,j×jni

而期望,考虑 f=inf+ninf+ik

移项得到 ff 的新增贡献为 nniik

于是对应的转移期望为 y=jni(fi,j+gi,j×nniik)

那么现在考虑转移的情况

  1. 新开一个段

x>gi+1,j+1,y>fi+1,j+1

  1. 接在一段左/右,没有其他贡献

2x>gi+1,j,2y>fi+1,j

  1. 在一段左/右旁边一个,这样会多一个假日

2x>gi+2,j,2y>fi+2,j

  1. 紧邻一个段左/右放,与另一侧的段距离为 1 补上一个假日造成拼接

2x>gi+2,j12y>fi+2,j1

  1. 与两个段距离都为 1 放了一个,补上两个假日造成拼接

x>gi+3j1y>fi+3,j1

因为我们是计数,所以必须推到最终状态 fn,0,那么工作日少于K天的可以只转移,不产生新的贡献

这样任何一个合法的方案最终都会汇总到 fn,0,所以直接输出就是对的

而且,可以验证的是 gn,0=1

code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int read(){
    int x = 0; char c = getchar();
    while(!isdigit(c))c = getchar();
    do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
    return x;
}

const int mod = 998244353;
const int maxn = 2005;
int qpow(int x, int y){
	int ans = 1;
	for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
	return ans;
}
int n, k, t;
int f[maxn][maxn], g[maxn][maxn];
int inv[maxn], pk[maxn];
void add(int &x, int y){x += y; if(x >= mod)x -= mod;}

int main(){
    freopen("holiday.in","r",stdin);
    freopen("holiday.out","w",stdout);
	n = read(), k = read(), t = read();
	for(int i = 1; i <= n; ++i)inv[i] = qpow(i, mod - 2), pk[i] = qpow(i, t);
	g[1][1] = 1;
	for(int i = 1; i < n - k; ++i)
		for(int j = 1; j <= i; ++j)if(g[i][j]){
			int x = 1ll * g[i][j] * j % mod * inv[n - i] % mod, x2 = x; add(x2, x);
			int y = 1ll * j * inv[n - i] % mod * (f[i][j] + 1ll * g[i][j] * n % mod * inv[n - i] % mod * pk[i] % mod) % mod, y2 = y; add(y2, y);
			add(g[i + 1][j + 1], x); add(f[i + 1][j + 1], y);
			add(g[i + 1][j], x2); add(f[i + 1][j], y2);
			add(g[i + 2][j], x2); add(f[i + 2][j], y2);
			add(g[i + 2][j - 1], x2); add(f[i + 2][j - 1], y2);
			add(g[i + 3][j - 1], x); add(f[i + 3][j - 1], y);
		}
	for(int i = n - k; i < n; ++i)
		for(int j = 1; j <= i; ++j)if(g[i][j]){
			int x = 1ll * g[i][j] * j % mod * inv[n - i] % mod, x2 = x; add(x2, x);
			int y = 1ll * f[i][j] * j % mod * inv[n - i] % mod, y2 = y; add(y2, y);
			add(g[i + 1][j + 1], x); add(f[i + 1][j + 1], y);
			add(g[i + 1][j], x2); add(f[i + 1][j], y2);
			add(g[i + 2][j], x2); add(f[i + 2][j], y2);
			add(g[i + 2][j - 1], x2); add(f[i + 2][j - 1], y2);
			add(g[i + 3][j - 1], x); add(f[i + 3][j - 1], y);
			
		}
	printf("%d\n",f[n][0]);
	cerr << g[n][0] << endl;
    return 0;
}

posted @   Chen_jr  阅读(37)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示