湖南省第一和多校前50全被队友葬送了,BC那么简单却给我送了十几发罚时,而且还没过!何神轩少请客吃饭是没得跑了
A String
解法:每次暴力枚举最长的合法串就行,签到
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e4 + 10;
char s[maxn], t[maxn], tmp[maxn];
queue<char> q;
int ok(int i, int j) {
int n = j - i + 1;
for (int k = 1; k < n; k++) {
for (int p = 0; p < n; p++) {
int q = k + p + i;
if (q > j)
q = q - j - 1 + i;
if (s[p + i] > s[q])
return 0;
else if (s[p + i] < s[q])
break;
}
}
return 1;
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%s", s);
int n = strlen(s);
int i = 0;
while (i < n) {
int j = i;
for (int pos = i + 1; pos < n; pos++)
if (ok(i, pos))
j = pos;
for (int k = i; k <= j; k++)
printf("%c", s[k]);
if (j == n - 1)
puts("");
else
printf(" ");
i = j + 1;
}
}
}
猜的结论:n>=3一定可以因式分解(百度验证猜对了),接下来我们分析n <=1,一定是不能分解,n=2时就是显然只有一种情况无法因式分解,那就是多项式中b^2 - 4ac<0
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 5e4 + 10;
ll a[maxn];
int main() {
int T;
scanf("%d", &T);
while (T--) {
int n;
cin>>n;
for (int i = 0; i <= n; i++)
cin>>a[i];
if (n <= 1) {
puts("Yes");
}
else if (n >= 3)
puts("No");
else {
if (a[1] * a[1] < 4 * a[0] * a[2])
puts("Yes");
else
puts("No");
}
}
}
题意:有n种树,每种树有高度h,数量p,和砍一颗的代价c,现在要你去砍树,使得最高的树的数量和大于剩下的所有树的数量和,求出最小代价
解法:我们按照高度排序后枚举高度,假设该数高度为最高 i,比它高的树全部要砍掉,我们求出所有比 i 低的树总和sum,如果sum >= 该树的数量v,就意味着我们还有再砍sum - v + 1颗比 i 的低的树,我们肯定优先砍便宜的树,但是我们知道c<=200,意味着我们最多200次就可以算出砍这些树的最小花费,最后跟答案取最小值即可
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5 + 10;
struct node {
int h, c, p;
bool operator<(const node& t) const {
return h < t.h;
}
} a[maxn];
ll suf[maxn], C[210];
int main() {
int n;
while (~scanf("%d", &n)) {
for (int i = 1; i <= n; i++)
scanf("%d%d%d", &a[i].h, &a[i].c, &a[i].p);
sort(a + 1, a + 1 + n);
ll ans = 1e18;
ll sum = 0;
suf[n + 1] = 0;
for (int i = n; i; i--)
suf[i] = suf[i + 1] + 1ll * a[i].c * a[i].p;
for (int i = 1; i <= 200; i++)
C[i] = 0;
for (int i = 1, j; i <= n; i = j) {
ll v = 0;
for (j = i; j <= n; j++)
if (a[j].h == a[i].h)
v += a[j].p;
else
break;
ll res = sum - v + 1;
ll tmp = suf[j];
if (res > 0) {
for (int k = 1; k <= 200 && res; k++)
if (C[k] <= res)
tmp += C[k] * k, res -= C[k];
else
tmp += res * k, res = 0;
}
ans = min(ans, tmp);
for (int k = i; k < j; k++)
sum += a[k].p, C[a[k].c] += a[k].p;
}
printf("%lld\n", ans);
}
}
题意:有一个初始序列(没有元素),有n次操作,每次操作给序列加这些元素: Li, Li+1,Li+2,,,Ri,然后求序列的中位数是多少,如果序列长度为偶数n,中位数为从小到大排序第n/2个数
解法:裸线段树,如果不卡空间,直接动态开点,如果不能动态开点,那我们把所有查询区间变成左开右闭区间然后离散化搞搞就行,没啥好讲的,线段树简单题
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 8e5 + 10;
ll sum[maxn * 4];
int S[maxn], L[maxn], R[maxn], tag[maxn * 4], n, sz;
#define ls o * 2
#define rs o * 2 + 1
#define m (l + r) / 2
void pushdown(int o, int l, int r) {
if (tag[o] == 0)
return;
tag[ls] += tag[o]; tag[rs] += tag[o];
sum[ls] += tag[o] * (S[m] - S[l - 1]);
sum[rs] += tag[o] * (S[r] - S[m]);
tag[o] = 0;
}
void up(int o, int l, int r, int ql, int qr) {
if (l >=ql && r <= qr) {
sum[o] += S[r] - S[l - 1];
tag[o]++;
return;
}
pushdown(o, l, r);
if (ql <= m)
up(ls, l, m, ql, qr);
if (qr > m)
up(rs, m + 1, r, ql, qr);
sum[o] = sum[ls] + sum[rs];
}
int qu(int o, int l, int r, ll v) {
if (l == r) {
int k = sum[o] / (S[r] - S[l - 1]);
return (v + k - 1)/ k + S[l - 1];
}
pushdown(o, l, r);
if (sum[ls] < v)
return qu(rs, m + 1, r, v - sum[ls]);
return qu(ls, l, m, v);
}
ll X[maxn], Y[maxn];
void init() {
ll A1, B1, C1, M1;
ll A2, B2, C2, M2;
cin>>n;
cin>>X[1]>>X[2]>>A1>>B1>>C1>>M1;
cin>>Y[1]>>Y[2]>>A2>>B2>>C2>>M2;
for (int i = 1; i <= n; i++) {
if (i > 2) {
X[i] = (A1 * X[i - 1] + B1 * X[i - 2] + C1) % M1;
Y[i] = (A2 * Y[i - 1] + B2 * Y[i - 2] + C2) % M2;
}
L[i] = min(X[i], Y[i]);
R[i] = max(X[i], Y[i]) + 1;
S[++sz] = L[i];
S[++sz] = R[i];
}
sort(S + 1, S + 1 + sz);
sz = unique(S + 1, S + 1 + sz) - S - 1;
}
int main() {
init();
for (int i = 1; i <= n; i++) {
L[i] = lower_bound(S + 1, S + 1 + sz, L[i]) - S;
R[i] = lower_bound(S + 1, S + 1 + sz, R[i]) - S;
up(1, 1, sz, L[i] + 1, R[i]);
ll v = sum[1] / 2;
if (sum[1] & 1)
v++;
printf("%d\n", qu(1, 1, sz, v));
}
}
题意:有n个石头,每个石头有初始能量Ei,每秒能增加Li能量,能量值上限Ci,初始时间为0,现在有m次操作,每次在 ti (ti < ti+1)时刻选择一段区间的石头,把他们能量全部拿走,然后这些石头当前能量就会变成0,求m次操作之后,我能得到的总能量。
解法:我们 i 从1开始枚举每个石头对所有操作的贡献,我们看哪些操作区间包含 i,并且用一个set把那些包含 i 的操作的时间存起来,我们知道 i 点从0开始加能量,最多需要 k = Ci / Li 的时间使得能量不会超过Ci,我们对这些时间排序,把所有时间间隔存到树状数组里,然后查询时间间隔小于等于k(即[1, k]) 的时间间隔的总和sum1,ans += sum1 * Li,k + 1的时间能量石肯定充满了,那么我们查询[k + 1, Max]看有多少个时间间隔sum2,然后ans += sum2 * Ei 即可,怎么维护这些时间间隔,假设当前我们存了时间 1 8,我们找一下所有操作左端点在 i 的时间 t,假设有个 t = 5,那么我们从树状数组中删除时间间隔 (8 - 7),插入时间间隔(5 - 1),(8 - 5),至于右端点,反过来即可。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5 + 10, N = 2e5;
set<int> st;
vector<int> S[maxn], T[maxn];
#define low(x) x&-x
int c[maxn * 2], E[maxn], L[maxn], C[maxn];
ll val[maxn * 2];
void up(int x, int v) {
for (int i = x; i <= N; i += low(i))
c[i] += v, val[i] += x * v;
}
int qu1(int l, int r) {
int res = 0;
for (int i = r; i; i -= low(i))
res += c[i];
for (int i = l - 1; i; i -= low(i))
res -= c[i];
return res;
}
ll qu2(int l, int r) {
ll res = 0;
for (int i = r; i; i -= low(i))
res += val[i];
for (int i = l - 1; i; i -= low(i))
res -= val[i];
return res;
}
int main() {
int ccsu_cat, Case = 0;
scanf("%d", &ccsu_cat);
while (ccsu_cat--) {
int n, l, r, t, m;
scanf("%d", &n);
for (int i = 1; i <= N; i++)
c[i] = val[i] = 0;
for (int i = 1; i <= n; i++)
scanf("%d%d%d", &E[i], &L[i], &C[i]);
scanf("%d", &m);
while (m--) {
scanf("%d%d%d", &t, &l, &r);
S[l].push_back(t);
T[r + 1].push_back(t);
}
ll ans = 0;
for (int i = 1; i <= n; i++) {
for (auto time : S[i]) {
st.insert(time);
auto it = st.find(time);
auto pre = it, nxt = it;
if (pre != st.begin()) {
pre--;
up(*it - *pre, 1);
}
nxt++;
if (nxt != st.end())
up(*nxt - *it, 1);
if (it != st.begin() && nxt != st.end())
up(*nxt - *pre, -1);
}
for (auto time : T[i]) {
auto it = st.find(time);
auto pre = it, nxt = it;
if (pre != st.begin()) {
pre--;
up(*it - *pre, -1);
}
nxt++;
if (nxt != st.end())
up(*nxt - *it, -1);
if (it != st.begin() && nxt != st.end())
up(*nxt - *pre, 1);
st.erase(time);
}
if (st.empty())
continue;
if (1ll * (*st.begin()) * L[i] + E[i] <= C[i])
ans += *st.begin() * L[i] + E[i];
else
ans += C[i];
if (L[i] == 0)
continue;
int k = C[i] / L[i];
ans += 1ll * qu1(k + 1, N) * C[i];
ans += 1ll * qu2(1, k) * L[i];
}
st.clear();
for (int i = 1; i <= n + 1; i++)
S[i].resize(0), T[i].resize(0);
printf("Case #%d: %lld\n", ++Case, ans);
}
}
靠,赛场写完没过样例,就放弃了,赛后发现数位dp多算进了x或着y为0的贡献,补上这个一发切了,我们求出所有的均不满足条件的(x, y)数量,即满足这两个条件:(x & y) <= c && (x^y) >= c,然后用A*B 减去就是答案,设d[i][sta1][sta2][lim1][lim2]为二进制长度为 i,第一,二个数字的限制情况分别为lim1,lim2,sta1 = 0 表示当前的(x & y) = c,1表示(x & y) < c,sta2 = 0表示当前的(x ^ y) = c,1表示(x ^ y) > c,然后进行基础的数位dp转移,设,a[pos],b[pos],c[pos]分别表示三个数二进制第pos位的状态,如果当前的sta1 = 0,且a[i] & b[i] > c[i],肯定不合法,如果a[i] & b[i] < c[i],那么下个状态的sta1就变成了1,如果sta1 = 1,那就肯定没有任何限制直接转移,sta2同理。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int C, a[30], b[30];
ll d[31][2][2][2][2];
ll dfs(int pos, int sta1, int sta2, int lim1, int lim2) {
if (pos < 0)
return 1;
if (d[pos][sta1][sta2][lim1][lim2] != -1)
return d[pos][sta1][sta2][lim1][lim2];
int up1 = lim1? a[pos] : 1;
int up2 = lim2? b[pos] : 1;
ll ans = 0;
for (int i = 0; i <= up1; i++)
for (int j = 0; j <= up2; j++) {
int x = 0;
if (C >> pos & 1)
x = 1;
if ((i & j) > x && !sta1)
continue;
if ((i ^ j) < x && !sta2)
continue;
int t1 = sta1 || ((i & j) < x);
int t2 = sta2 || ((i ^ j) > x);
ans += dfs(pos - 1, t1, t2, lim1 && (i == up1), lim2 && (j == up2));
}
d[pos][sta1][sta2][lim1][lim2] = ans;
return ans;
}
int main() {
int T;
cin>>T;
while (T--) {
int x, y;
cin>>x>>y>>C;
ll ans = 1ll * x * y + max(x - C + 1, 0) + max(y - C + 1, 0);
for (int i = 0; i < 30; i++)
a[i] = b[i] = 0;
memset(d, -1, sizeof(d));
for (int i = 0; x; i++, x /= 2)
a[i] = x % 2;
for (int i = 0; y; i++, y /= 2)
b[i] = y % 2;
printf("%lld\n", ans - dfs(29, 0, 0, 1, 1));
}
}
题意:给你一个N,M,然后你可以任意构造一个 k * k的矩阵,使得矩阵内每个元素最少是M,且任意不同行不同列的 k 个元素总和不超过N且都相同,问有多少种构造方法。
解法:隔板法好题,我们枚举k,我们可以把每个元素减去M,那么就相当于N减去 k * M,简化问题并且不影响答案,然后我们枚举元素总和T(T <= N), 第二个限制条件的实质是我们抽取一些行一些列,使得这些行一整行加一些数,这些列一整列加一些数,使得加的数的总和是T,再转化一下,现在有 k * 2 + T个1摆在一排,我们要给他分成2 * k份,每一份1的个数x减去1对应某行或者某列整行整列加的数,我们都知道隔板法,不知道?那就继续看:每两个1之间有一个隔间,那么k *2 + T有k * 2 - 1 +T个隔间,我们选择k * 2 - 1个隔间就可以把这些1分成k * 2份,那么好像当前的答案就是C(k * 2 - 1 + T,k * 2 - 1),实则算多了,假设2 * 2的矩阵全部是1,那么是不是可以理解为选取两行+1或者选取两列+1?像这种情况,为了避免重复算,如果整个矩阵最小值都x>=1,我们假设只能是所有行都加了x,那么就可以这么去重,我们预先给所有列+1,相当于k + T个1分成2 * k份,那么真正的答案就要减去 C(k - 1 + T,k * 2 - 1)。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 6010, mod = 998244353;
ll p[maxn], inv[maxn];
void add(int &x, int y) {
x += y;
if (x >= mod)
x -= mod;
if (x < 0)
x += mod;
}
ll ksm(ll x, int y) {
ll res = 1;
while (y) {
if (y & 1)
res = res * x % mod;
x = x * x % mod;
y /= 2;
}
return res;
}
ll C(int n, int m) {
if (n < m)
return 0;
return p[n] * inv[m] % mod * inv[n - m] % mod;
}
int main() {
int T;
cin>>T;
p[0] = inv[0] = 1;
for (int i = 1; i < maxn; i++) {
p[i] = p[i - 1] * i % mod;
inv[i] = ksm(p[i], mod - 2);
}
while (T--) {
int n, m, ans = 0;
cin>>n>>m;
for (int k = 1; k <= n; k++) {
int N = n - m * k;
if (N < 0)
break;
for (int T = 0; T <= N; T++) {
add(ans, C(2 * k - 1 + T, 2 * k - 1));
add(ans, -C(k - 1 + T, 2 * k - 1));
}
}
cout<<ans<<'\n';
}
}

本文精选了算法竞赛中的经典题目,包括字符串匹配、多项式分解、树形结构、线段树、动态规划等,提供了详细的解题思路和代码实现。
357





