Wannafly挑战赛10
A.小H和迷宫 模拟
题意
有一个怪兽,血量有N点,小H有三瓶魔法药水,分别可以使怪兽损失a%、b%、c%的血量(之后怪兽的血量会向下取整),小H想合理地运用这三瓶药水,使怪兽失去尽可能多的血量, 问最多可以消耗的血量 注意:每瓶药水只能用一次(5<=n<=1e9,0<=a,b,c<=100)
分析
贪心,感性的可以发现,先用大的最优
一开始wa成狗,最后数输出小数(浮点数)导致出错
B.小H和密码 dp
题意
给出n个长度为m字符串(由小写字母和#组成,其中‘#’代表空字符),现有q个询问,每个询问为一个长度小于10000的字符串,问是否可以让n个字符串,每个字符串都选择一个字母,组成当前询问的字符串(空字符直接忽略即可) (2<=n,q<=300,2<=m<=27)
分析
看这数据范围,以及撕烤一下,不难想到dp,关键是如何定义dp,显然若想暴力dp存储所有可能字符串是不可能的,27^n直接爆炸
看了题解
定义:dp[i][j]:前i个字符串匹配前询问字符串前j个是否满足,
转移:若第i个字符串有第j个字符,dp[i][j]|=dp[i-1][j-1]
若第i个字符串有#(空字符),dp[i][j]|=dp[i-1][j]
答案:若询问字符串长度为len,dp[n][len]的真假性即为答案
trick:dp[0][0]=1,即开始没有匹配的时候一定为真
#include<bits/stdc++.h> #define ll long long using namespace std; int dp[302][302]; int vis[302][302]; int n, m, q; char s[10005]; int main() { scanf("%d%d%d", &n, &m, &q); for(int i=1;i<=n;i++){ scanf("%s", s); for(int j=0;j<m;j++){ vis[i][s[j]]=1; } } while(q--) { scanf("%s", s+1); int len=strlen(s+1); if(len>n) { printf("NO\n"); continue; } memset(dp,0,sizeof(dp)); dp[0][0]=1; for(int i=1;i<=n;i++){ for(int j=0;j<=len;j++){ if(vis[i]['#']) dp[i][j]|=dp[i-1][j]; if(vis[i][s[j]]) dp[i][j]|=dp[i-1][j-1]; } } if(dp[n][len]) printf("YES\n"); else printf("NO\n"); } return 0; }
C.小H和游戏 套路
题意
小H正在玩一个战略类游戏,她可以操纵己方的飞机对敌国的N座城市(编号为1~N)进行轰炸
敌国的城市形成了一棵树,小H会依次进行Q次轰炸,每次会选择一个城市A进行轰炸,和这座城市距离不超过2的城市都会受损(这里距离的定义是两点最短路径上的边数),轰炸结束后,小H还想知道当前城市A受损的次数,给出n,q和n-1条边(n,q<=750000)
敌国的城市形成了一棵树,小H会依次进行Q次轰炸,每次会选择一个城市A进行轰炸,和这座城市距离不超过2的城市都会受损(这里距离的定义是两点最短路径上的边数),轰炸结束后,小H还想知道当前城市A受损的次数,给出n,q和n-1条边(n,q<=750000)
分析
将无向图转换为有根树(直接dfs一次即可,顺便记录下每个点的父节点)
对一个点操作,只和它的它自己,它的儿子结点,儿子的儿子结点以及,父亲结点,父亲的父亲的结点形成影响
所以只要将其记录下来即可,开一个二维数组sum[u][]:表示从u出发,路径长为0,1,2的受损次数,注意一下,别重复统计即可
#include<bits/stdc++.h> #define ll long long #define pb push_back using namespace std; const int maxn = 750000+2; int fa[maxn]; int n,q; vector<int>G[maxn]; int sum[maxn][3]; void dfs(int now, int f) { fa[now]=f; for(int i=0;i<int(G[now].size());i++) { if(G[now][i]==f) continue; dfs(G[now][i], now); } } int main() { scanf("%d%d", &n, &q); int u, v; for(int i=1;i<=n-1;i++) { scanf("%d%d", &u, &v); G[u].pb(v); G[v].pb(u); } dfs(1,0); while(q--) { int k; scanf("%d", &k); sum[fa[k]][0]++; sum[fa[k]][1]++; sum[fa[fa[k]]][0]++; sum[k][1]++; sum[k][2]++; printf("%d\n", sum[fa[fa[k]]][2]+sum[fa[k]][1]+sum[k][0]); } return 0; }
D.小H的询问
题意
小H给你一个数组{a},要求支持以下两种操作:
1. 0 l r(1<=l<=r<=n),询问区间[l,r]中权值和最大的有效子区间的权值和,一个子区间被认为是有效的当且仅当这个子区间中没有两个相邻的偶数或者奇数。
2. 1 x v(1<=x<=n,-109<=v<=109),将a[x]的值修改为v。
输入描述:
第一行读入两个正整数n,m(1<=n,m<=105)
第二行读入n个整数,第i个表示a[i](-109 <= a[i] <= 109)
接下来m行,每行三个数表示操作,描述见题目描述。
输出描述:
输出每个询问的答案
分析
线段树基础题
线段树每个区间维护区间左,右端点的奇偶性,左,右端的最大区间和,以及整个区间最大子区间,注意细节
#include <bits/stdc++.h> using namespace std; #define rep(i, a, b) for (int i(a); i <= (b); ++i) #define dec(i, a, b) for (int i(a); i >= (b); --i) #define ls i << 1 #define rs i << 1 | 1 #define mid ((l + r) >> 1) #define lson ls, l, mid #define rson rs, mid + 1, r typedef long long LL; const int N = 1e5 + 10; struct node{ LL lc, rc, ret; int lo, ro, flag; } t[N << 2]; int n, m; void pushup(int i){ //t[i].c = t[ls].c + t[rs].c; t[i].ret = max(t[ls].ret, t[rs].ret); t[i].flag = 0; t[i].lc = t[ls].lc; t[i].rc = t[rs].rc; t[i].lo = t[ls].lo; t[i].ro = t[rs].ro; if (t[ls].ro ^ t[rs].lo){ t[i].ret = max(t[i].ret, t[ls].rc + t[rs].lc); if (t[ls].flag) t[i].lc = max(t[i].lc, t[ls].rc + t[rs].lc); if (t[rs].flag) t[i].rc = max(t[i].rc, t[rs].lc + t[ls].rc); if (t[ls].flag && t[rs].flag) t[i].flag = 1; } } void build(int i, int l, int r){ if (l == r){ scanf("%lld", &t[i].ret); t[i].lc = t[i].rc = t[i].ret; t[i].lo = t[i].ro = (t[i].ret & 1); t[i].flag = 1; return; } build(lson); build(rson); pushup(i); } void update(int i, int l, int r, int x, LL val){ if (l == x && l == r){ t[i].ret = t[i].lc = t[i].rc = val; t[i].lo = t[i].ro = (val & 1); t[i].flag = 1; return; } if (x <= mid) update(lson, x, val); else update(rson, x, val); pushup(i); } node query(int i, int l, int r, int L, int R){ if (L <= l && r <= R) return t[i]; if (R <= mid) return query(lson, L, R); if (L > mid) return query(rson, L, R); node ta = query(lson, L, mid); node tb = query(rson, mid + 1, R); node ans; // ans.c = ta.c + tb.c; ans.ret = max(ta.ret, tb.ret); ans.flag = 0; ans.lc = ta.lc; ans.rc = tb.rc; ans.lo = ta.lo; ans.ro = tb.ro; if (ta.ro ^ tb.lo){ ans.ret = max(ans.ret, ta.rc + tb.lc); if (ta.flag) ans.lc = max(ans.lc, ta.rc + tb.lc); if (tb.flag) ans.rc = max(ans.rc, tb.lc + ta.rc); if (ta.flag && tb.flag) ans.flag = 1; } return ans; } int main(){ scanf("%d%d", &n, &m); build(1, 1, n); while (m--){ int op; scanf("%d", &op); if (op == 0){ int x, y; scanf("%d%d", &x, &y); node ans = query(1, 1, n, x, y); printf("%lld\n", ans.ret); } else{ int x; LL y; scanf("%d%lld", &x, &y); update(1, 1, n, x, y); } } return 0; }
补完总结:
像D这种标准的线段树细节题, 只要弄清楚需要哪些变量来表示这些关系,以及这些关系之间的关系,pushup之间的先后顺序,才能解题
要么优秀要么生锈