初见安~这里是几百年没打洛谷月赛了的樱狸(什
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。所以设表示从i走到i+1的期望步数。则如果i有一条返祖边到点v,那么我们需要求从v到i的期望步数,也就是:
。
那我们不如直接维护一个表示f的前缀和。就可以得到:
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步。
最后所求就是。
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——