洛谷·9月月赛I【including [Cnoi2020]子弦,[Cnoi2020]雷雨,[Cnoi2020]梦原,[Cnoi2020]线形生物

初见安~这里是几百年没打洛谷月赛了的樱狸(什

1、P6832 [Cnoi2020]子弦

不要想复杂了。其实就是单纯问你出现次数最多的那个字符出现了几次。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<map>
using namespace std;
char s[10000007];
int cnt[30];
signed main() {
    scanf("%s", s);
    register int len = strlen(s);
    for(register int i = 0; i < len; i++) cnt[s[i] - 'a']++;
    int ans = 0;
    for(register int i = 0; i < 26; i++) ans = max(ans, cnt[i]);
    printf("%d\n", ans);
    return 0;
}

 

2、P6833 [Cnoi2020]雷雨

想过去重,可惜不可求。但是如果去重的话就需要知道从闪电、A和B出发到达地图每个点的距离。而我们知道了这些距离就可以直接枚举一个作为三岔路口的那个点,最后求的一定最优,重复的会被不重复的取代。

至于求法,暴力bfs就行了。【spfa?

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<map>
#define maxn 1005
using namespace std;
typedef long long ll;
const ll INF = 1e18;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

int n, m, a, b, c;
ll R[maxn][maxn], dis_a[maxn][maxn], dis_b[maxn][maxn], dis_c[maxn][maxn], dis[maxn][maxn];
struct node {
	int x, y; ll d;
	bool operator < (const node &a) const {return a.d < d;}
};

priority_queue<node> q;
bool vis[maxn][maxn];
int dir[4][2] = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};//bfs
bool in(int a, int b) {return 0 < a && a <= n && 0 < b && b <= m;}
void get_dis(int s, int t) {
	memset(dis, 0x3f, sizeof dis); dis[s][t] = R[s][t];
	q.push({s, t, dis[s][t]});
	while(q.size()) {
		node u = q.top(); q.pop(); vis[u.x][u.y] = false;//经典spfa
		for(int i = 0; i < 4; i++) {
			register int tx = u.x + dir[i][0], ty = u.y + dir[i][1];
			if(in(tx, ty) && dis[tx][ty] > dis[u.x][u.y] + R[tx][ty]) {
				dis[tx][ty] = dis[u.x][u.y] + R[tx][ty];
				if(!vis[tx][ty]) vis[tx][ty] = true, q.push({tx, ty, dis[tx][ty]});
			}
		}
	}
}

signed main() {
	n = read(), m = read(), a = read(), b = read(), c = read();
	for(int i = n; i; i--) for(int j = 1; j <= m; j++) R[i][j] = read();
	get_dis(n, a); memcpy(dis_a, dis, sizeof dis_a);//由于dis是二维的我不会传……只有memcpy了
	get_dis(1, b); memcpy(dis_b, dis, sizeof dis_b);
	get_dis(1, c); memcpy(dis_c, dis, sizeof dis_c);
	
	ll ans = INF;
	for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) {//枚举三岔路口
		//printf("at %d %d:%lld + %lld + %lld - %lld\n", i, j, dis_c[i][j], dis_a[i][j], dis_b[i][j], 1ll * 2 * R[i][j]), 
		ans = min(ans, dis_c[i][j] + dis_a[i][j] + dis_b[i][j] - 1ll * 2 * R[i][j]);//核心语句
	}
		
	printf("%lld\n", ans);
	return 0;
}

 

3、P6834 [Cnoi2020]梦原

初见杀。【特别是对于我这个期望特别菜的人】

考虑使用魔法。因为树本身是联通的,所以依次断掉的点可以看成是从小到大的。换言之,一个点如果接在了一个值比他小的点上,那么那个点都取完果子了你还没取完,你就要增加取的次数。所以——一个点会对答案产生贡献当且仅当他接在了一个值比他小的点上,并且贡献为两者的值差。所以对每个点求出可以接的点里面,比他小的点数和这些点的值的和即可。可以用树状数组维护。注意最多到前K个点,而且这个区间大小可能不足K(i<=K)。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<map>
#define maxn 1000006
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int mx = 1e6;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

int n, m;
ll t[maxn], cnt[maxn];
ll a[maxn], srt[maxn], dfn[maxn], tot = 0, ans = 0;
ll pw(ll a, ll b) {ll res = 1; while(b) {if(b & 1) res = res * a % mod; a = a * a % mod, b >>= 1;} return res;}
void add(ll *t, int x, ll d) {for(; x <= mx; x += (x & -x)) t[x] = (t[x] + d) % mod;}
ll ask(ll *t, int x) {ll res = 0; for(; x; x -= (x & -x)) res = (res + t[x]) % mod; return res;}

signed main() {
	n = read(), m = read();
	for(int i = 1; i <= n; i++) a[i] = read(), srt[i] = a[i];
	sort(srt + 1, srt + 1 + n);//离散化一下
	for(int i = 1; i <= n; i++) dfn[i] = lower_bound(srt + 1, srt + 1 + n, a[i]) - srt;
	
	add(t, dfn[1], a[1]); add(cnt, dfn[1], 1);//t是和,cnt是个数
	for(int i = 2; i <= n; i++) {
		if(i - m - 1 > 0) add(t, dfn[i - m - 1], mod - a[i - m - 1]), add(cnt, dfn[i - m - 1], -1); //注意边界
		ll tmp = ask(cnt, dfn[i]);//tmp是个数
		ans = (ans + (tmp * a[i] % mod - ask(t, dfn[i]) + mod) % mod * pw(1ll * min(i - 1, m), mod - 2) % mod) % mod;
		add(t, dfn[i], a[i]), add(cnt, dfn[i], 1);//上一行是核心,同样注意边界
	}
	printf("%lld\n", (ans + a[1]) % mod);//a[1]是必然要操作的次数,直接加。
	return 0;
}

 

4、P6835 [Cnoi2020]线形生物

乍一看很难的样子【全机房就我没AK就因为这个题没做出来QAQ】。

考虑一个点该如何到达下一个点。一种是直接走i->i+1,一种是走返祖边回去,再来到i,再走i->i+1。所以设f_i表示从i走到i+1的期望步数。则如果i有一条返祖边到点v,那么我们需要求从v到i的期望步数,也就是:

\sum_{x=v}^{i-1}f_x

那我们不如直接维护一个g_i表示f的前缀和。就可以得到:

f_u=(\sum_{u->v(v !=u+1)}g_{i-1}-g_{v-1})+deg[u]+1

deg是u的出度(没有算u->u+1这条边)。因为i可能走返祖边回去,所以每走了一条返祖边期望步数必然+1;而回到u后去u+1也必然走一步,所以要加上deg+1。

你可能会疑惑为什么这个式子是对的,为什么不用乘类似1/deg之类的概率。【好吧可能只有我会有这个疑惑】因为f已经是表示期望了,期望可以线性直接加。那为什么加的是deg+1而不是2*deg?【大概可以这么理解吧】因为要到点u,有deg种情况,每种情况对应的期望必然要走一步返祖边;而从u到u+1只有一条边可以走,期望只有1步。

最后所求就是g_n

lpr大佬:“你做过期望的题就应该知道怎么推啊。”

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<map>
#define maxn 1000006
using namespace std;
typedef long long ll;
const int mod = 998244353;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

struct edge {int to, nxt;} e[maxn];
int head[maxn], k = 0;
void add(int u, int v) {e[k] = {v, head[u]}; head[u] = k++;}

int T, n, m, ind[maxn];
ll f[maxn], g[maxn];
signed main() {
	memset(head, -1, sizeof head);
	T = read(), n = read(), m = read();
	for(int i = 1, u, v; i <= m; i++) u = read(), v = read(), add(u, v), ind[u]++;
	for(int u = 1; u <= n; u++) {
		for(int i = head[u], v; ~i; i = e[i].nxt) 
			v = e[i].to, f[u] = 1ll * (f[u] + g[u - 1] - g[v - 1] + mod) % mod;
		f[u] = 1ll * (f[u] + ind[u] + 1) % mod, g[u] = (g[u - 1] + f[u]) % mod;
	}//这里我用的ind表示deg。
	printf("%lld\n", g[n]);
	return 0;
}

整体难度好像不是很大。题目质量后面两个挺好的。

迎评:)
——End——

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值