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. 翻转有向图
一直在想 过程中的树边返祖边横叉边,然后啥也不会
考虑一条边的影响,其实无非几种情况,而且只用考虑他连接的两个点
如果他有影响,那么一定会影响他连接的两个点的关系
- 之前不是SCC,反向后为SCC
存在 另一条路径,不存在 路径
- 之前不是,之后不是
不存在 另一条路径, 不存在 路径
- 之前是,之后不是
不存在 另一条路径, 存在 路径
- 之前之后都是
存在 另一条路径, 存在 路径
然后我们需要得到
是否存在多于一条路径, 是否存在路径
存在路径直接从每个点出发扫就行
如何解决前面那个?
一个关键的点在于我们只关心由一条边连接的 ,
那么如果存在其他路径,我们把边表正序倒序,两次 从 出发到达 的边一定不是同一条
于是可以进行两次染色
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. 在表格里造序列
题解说的很好
还有一种做法,复杂度不会分析
就是用莫比乌斯反演容斥求
整除分块
外面那个 我考场没想到,但是也可以求
做法是枚举 然后莫反
带上整除分块+杜教筛求 的前缀和也能做
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
学习的 写法,只能说 太巨了,一个概率题,做成了计数题
显然的一点是考虑连续段 , 但是我一直纠结于如何去掉不合法的状态,然而事实上这东西根本不用考虑,取最终合法的答案即可,不懂继续看
设 为有 天假日, 他们组成了 个连续段的概率
表示期望
因为我们用类似计数的方法来做,所以在最终确定答案之前,所有状态的概率和不一定为
转移考虑对连续段的影响
每次转移,有 的概率选择到一个工作日
因为有 个连续段,在每个连续段后操作对应不同的方案,一共有 种方案
于是转移概率为
而期望,考虑
移项得到 对 的新增贡献为
于是对应的转移期望为
那么现在考虑转移的情况
- 新开一个段
- 接在一段左/右,没有其他贡献
- 在一段左/右旁边一个,这样会多一个假日
- 紧邻一个段左/右放,与另一侧的段距离为 补上一个假日造成拼接
- 与两个段距离都为 放了一个,补上两个假日造成拼接
因为我们是计数,所以必须推到最终状态 ,那么工作日少于天的可以只转移,不产生新的贡献
这样任何一个合法的方案最终都会汇总到 ,所以直接输出就是对的
而且,可以验证的是
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】