完型填空
思路:背包
- 用dp[i][j[k][h]表示在选了i道题的情况下,其中选了j道a,k道b,h道c,i-(j+k+h)道d时的最大期望
- 注意是每层循环,每道题选的数目不超过n/4,在选第i道题目时,之前选的所有题目和应该小于i
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 110;
int dp[N][30][30][30];
int a[N][5];
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; ++i)cin >> a[i][1] >> a[i][2] >> a[i][3] >> a[i][4];
for (int i = 1; i <= n; ++i)for (int j = 0; j <= n / 4 && j < i; ++j)for (int k = 0; k <= n / 4 && k + j < i; ++k)for (int h = 0; h <= n / 4 && j + k + h < i; ++h)
{
int tmp = dp[i - 1][j][k][h];
dp[i][j][k][h] = max(dp[i][j][k][h], tmp + a[i][4]);
dp[i][j + 1][k][h] = max(dp[i][j + 1][k][h], tmp + a[i][1]);
dp[i][j][k + 1][h] = max(dp[i][j][k + 1][h], tmp + a[i][2]);
dp[i][j][k][h + 1] = max(dp[i][j][k][h + 1], tmp + a[i][3]);
}
cout << dp[n][n / 4][n / 4][n / 4] << endl;
return 0;
}
摘苹果
思路:线段树裸题线段树
- 注意优化是显然我们每次需要具体到每个点去除3,但是我们如果[l,r]的区间没有一个数大于10,我们还需要进去这个区间吗?
- 所以每个区间维护一个区间最大值maxn,如果区间最大值都小于10,这个区间不用更新
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e5 + 10;
int a[N];
struct tree
{
int l, r;
ll sum, cnt, maxn;
} t[N << 2];
void build(int l, int r, int p)
{
t[p].l = l, t[p].r = r, t[p].cnt = 0;
if (l == r)
{
t[p].sum = t[p].maxn = a[l];
if (t[p].sum < 100)t[p].cnt = 1;
return;
}
int mid = l + ((r - l) >> 1);
build(l, mid, p << 1);
build(mid + 1, r, p << 1 | 1);
t[p].sum = t[p << 1].sum + t[p << 1 | 1].sum;
t[p].cnt = t[p << 1].cnt + t[p << 1 | 1].cnt;
t[p].maxn = max(t[p << 1].maxn, t[p << 1 | 1].maxn);
}
void update(int l, int r, int p)
{
if (t[p].maxn < 10)return;
if (t[p].l == t[p].r)
{
t[p].sum = t[p].maxn = t[p].sum * 2 / 3;
if (t[p].sum < 100)t[p].cnt = 1;
return;
}
int mid = t[p].l + ((t[p].r - t[p].l) >> 1);
if (l <= mid)update(l, r, p << 1);
if (r > mid)update(l, r, p << 1 | 1);
t[p].sum = t[p << 1].sum + t[p << 1 | 1].sum;
t[p].cnt = t[p << 1].cnt + t[p << 1 | 1].cnt;
t[p].maxn = max(t[p << 1].maxn, t[p << 1 | 1].maxn);
}
ll asksum(int l, int r, int p)
{
if (t[p].r <= r && l <= t[p].l)return t[p].sum;
int mid = t[p].l + ((t[p].r - t[p].l) >> 1);
ll ans = 0;
if (l <= mid)ans += asksum(l, r, p << 1);
if (r > mid)ans += asksum(l, r, p << 1 | 1);
return ans;
}
ll askcnt(int l, int r, int p)
{
if (t[p].r <= r && l <= t[p].l)return t[p].cnt;
int mid = t[p].l + ((t[p].r - t[p].l) >> 1);
ll ans = 0;
if (l <= mid)ans += askcnt(l, r, p << 1);
if (r > mid)ans += askcnt(l, r, p << 1 | 1);
return ans;
}
int main()
{
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i)cin >> a[i];
int op, x, y;
build(1, n, 1);
while (m--)
{
cin >> op >> x >> y;
if (op == 1)update(x, y, 1);
else if (op == 2)cout << askcnt(x, y, 1) << endl;
else if (op == 3)cout << asksum(x, y, 1) << endl;
}
return 0;
}
前缀复制机
思路:
名字暗示的很明显了,就是前缀函数裸题前缀函数与KMP算法
- 我们处理好字符串的前缀和后,从后往前遍历。这样贪心即是最优解
- 不从前是因为,后面过去,能匹配多大的前缀已经求出来了,而如果是从前往后,假设你这里可以先匹配3个,后面假设没得匹配。但是,假设如果你这里先匹配2个,后面就可以全部匹配完。那是不是后面这种方案更好。
- 所以从后往前推是知道了前面的所有情况做出的最优解(每次尽可能减少后面的长度)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef unsigned long long ull;
typedef pair<long long, long long> pll;
typedef pair<int, int> pii;
//double 型memset最大127,最小128
//std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
const int INF = 0x3f3f3f3f; //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int N = 1e5 + 10;
int p[N];
int main()
{
int t;
cin >> t;
while (t--)
{
int n;
string s;
cin >> n >> s;
p[0] = 0;
for (int i = 1; i < (int)s.size(); ++i)
{
int j = p[i - 1];
while (s[j] != s[i] && j > 0)j = p[j - 1];
if (s[j] == s[i])j++;
p[i] = j;
}
//从0开始,所有num最后下标是n-1
int num = n - 1;
int ans = 0;
while (num >= 0)
{
ans++;
if (!p[num])num--;
else
{
int tmp = p[num];
while (tmp * 2 > num + 1 && tmp > 0)tmp = p[tmp - 1];
num -= tmp;
}
}
cout << ans << endl;
}
map<int, int *>mp;
return 0;
}