Codeforces Round #574 (Div. 2)
题目链接:https://codeforces.com/contest/1195
好像做过这一场,这场的CDE都比较有印象,特别是E,随便掏个数据结构就可以做了。
A - Drinks Choosing
题意:每份饮料恰好有2支,有 \(n\) 个人 \(n\) 种饮料,每个人有自己最喜欢的一种饮料,要恰好买 \(\lceil\frac{n}{2}\rceil\) 份饮料,求最多有多少人喝到自己最喜欢的饮料?
题解:某种饮料每满2就肯定买一份,最后肯定剩下一群1或者0,随便买一些1。
int cnt[1005];
void test_case() {
int n, k;
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++i) {
int x;
scanf("%d", &x);
++cnt[x];
}
int cnt2 = 0;
for(int i = 1; i <= k; ++i)
cnt2 += cnt[i] / 2;
printf("%d\n", (n + 1) / 2 + cnt2);
}
B - Sport Mafia
题意:有 \(n\) 次操作,其中若是eat操作则会使得糖果数量 -1 ,若是第i次put操作则会使得糖果数量 +i ,已知最后恰好剩下 \(k\) 颗糖果,保证有唯一解,求eat了多少次。
题解:显然eat越多就会越少,满足单调性可以二分。
其实设put了x次,则有:
\(\frac{x(x+1)}{2}-(n-x)=k\)
即 \(x^2+x-2n+2x=2k\)
即 \(x^2+3x+(-2n-2k)=0\)
由韦达定理可知两根一正一负,可以直接解这个方程的正根。
int n, k;
bool check(int e) {
ll p = n - e;
ll sump = (1 + p) * p / 2;
return (sump - e) >= k;
}
void test_case() {
scanf("%d%d", &n, &k);
int L = 0, R = n;
while(1) {
int M = (L + R) >> 1;
if(L == M) {
if(check(R)) {
printf("%d\n", R);
return;
}
assert(check(L));
printf("%d\n", L);
return;
}
if(check(M))
L = M;
else
R = M - 1;
}
}
C - Basketball Exercise
题意:有两行正数,要从两行之间交替选,求选出的和的最大值。
题解:直接dp就可以。设 \(dp[i][0]\) 表示恰好以第 \(i\) 个位置的第 \(0\) 行结尾的最大值,那么有个显然的转移方程在程序中。
int a[100005][2];
ll dp[100005][2];
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i][0]);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i][1]);
dp[1][0] = a[1][0];
dp[1][1] = a[1][1];
for(int i = 2; i <= n; ++i) {
dp[i][0] = max(dp[i - 1][1], dp[i - 2][1]) + a[i][0];
dp[i][1] = max(dp[i - 1][0], dp[i - 2][0]) + a[i][1];
}
printf("%lld\n", max(dp[n][0], dp[n][1]));
}
D1 - Submarine in the Rybinsk Sea (easy edition)
见下
*D2 - Submarine in the Rybinsk Sea (hard edition)
题意:定义一个合成函数 \(f(a,b)\) 它的合成规则是先把 \(b\) 的个位放在个位,再把 \(a\) 的个位放在十位,依次交替直到其中一个放完,剩下的就全放剩下那个数的剩余的高位。给 \(n\) 个数,求 \(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}f(a_i,a_j)\) 。
题解:对于某个 \(x\) 而言,所有相同长度的 \(y\) 与 \(x\) 进行 \(f(x,y)\) 合成之后, \(x\) 对结果的贡献是固定的(可以通过处理 \(x\) 的各位在结果的哪位中确定)。所以可以统计各种长度的数量,再算贡献。好像这题在前几天也写过一个很像的。
int n;
ll a[100005];
ll sumL[100005][11];
ll sumR[100005][11];
int cntlen[11];
ll tmp[11];
ll pow10[21];
void calcL(int i) {
ll x = a[i];
for(int j = 1; j <= 10; ++j) {
tmp[j] = x % 10;
x /= 10;
//printf("%lld", tmp[j]);
}
//printf("\n");
for(int Rlen = 1; Rlen <= 10; ++Rlen) {
sumL[i][Rlen] = 0;
for(int j = 1; j <= Rlen; ++j)
sumL[i][Rlen] += pow10[2 * j - 1] * tmp[j];
for(int j = Rlen + 1; j <= 10; ++j)
sumL[i][Rlen] += pow10[j + Rlen - 1] * tmp[j];
sumL[i][Rlen] %= MOD;
//printf("a[%d]=%lld sumLRlen[%d]=%lld\n", i, a[i], Rlen, sumL[i][Rlen]);
}
//puts("");
}
void calcR(int i) {
ll x = a[i];
for(int j = 1; j <= 10; ++j) {
tmp[j] = x % 10;
x /= 10;
//printf("%lld", tmp[j]);
}
//printf("\n");
for(int Llen = 1; Llen <= 10; ++Llen) {
sumR[i][Llen] = 0;
for(int j = 1; j <= Llen; ++j)
sumR[i][Llen] += pow10[2 * j - 1 - 1] * tmp[j];
for(int j = Llen + 1; j <= 10; ++j)
sumR[i][Llen] += pow10[j + Llen - 1] * tmp[j];
sumR[i][Llen] %= MOD;
//printf("a[%d]=%lld sumRLlen[%d]=%lld\n", i, a[i], Llen, sumR[i][Llen]);
}
//puts("");
}
void calcLen(int i) {
int len = 0;
ll x = a[i];
while(x) {
++len;
x /= 10;
}
//printf("i=%d len=%d\n", i, len);
++cntlen[len];
}
void test_case() {
pow10[0] = 1;
for(int i = 1; i <= 20; ++i)
pow10[i] = pow10[i - 1] * 10 % MOD;
scanf("%d", &n);
for(int i = 1; i <= n; ++i)
scanf("%lld", &a[i]);
for(int i = 1; i <= n; ++i) {
calcL(i);
calcR(i);
calcLen(i);
}
ll sum = 0;
for(int i = 1; i <= n; ++i) {
for(int Rlen = 1; Rlen <= 10; ++Rlen)
sum += sumL[i][Rlen] * cntlen[Rlen] % MOD;
for(int Llen = 1; Llen <= 10; ++Llen)
sum += sumR[i][Llen] * cntlen[Llen] % MOD;
sum %= MOD;
}
printf("%lld\n", sum);
}
E - OpenStreetMap
题意:给一个 \(n*m(1\leq n,m \leq 3000)\) 的矩阵,然后有一个 \(a*b(1\leq a \leq n, 1\leq b \leq m)\) 框,这个框正好遍历整个矩阵,求框中的最小值的和。
题解:用单调队列或者双栈队列,每列开一个这样的队列。逐行扫描,每次每列的队列Q中都存当前的 \(a\) 行的元素,那么转移的时候就可以直接把这些元素的最小值取出来了。扫描完一行之后命令所有的每列的队列Q都向下移动一个位置。
struct Queue {
static const int MAXN = 3005;
static const int INF = 1061109567;
int s1[MAXN + 5], s2[MAXN + 5];
int s1top, s2top, s1min;
void Clear() {
s1top = 0;
s2top = 0;
s2[0] = INF;
s1min = INF;
}
void Push(int x) {
s1[++s1top] = x;
s1min = min(s1min, x);
}
void Pop() {
if(s2top)
--s2top;
else {
while(s1top)
s2[++s2top] = min(s2[s2top - 1], s1[s1top--]);
--s2top;
s1min = INF;
}
}
int Size() {
return s1top + s2top;
}
int Min() {
return min(s2[s2top], s1min);
}
} Q[3005], RQ;
int g[3005 * 3005];
int h[3005][3005];
void test_case() {
int n, m, a, b;
scanf("%d%d%d%d", &n, &m, &a, &b);
int g0, x, y, z;
scanf("%d%d%d%d", &g0, &x, &y, &z);
g[0] = g0;
for(int i = 1; i <= n * m; ++i)
g[i] = (1ll * g[i - 1] * x + y) % z;
int top = 0;
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= m; ++j) {
h[i][j] = g[top++];
//printf("%d%c", h[i][j], " \n"[j == m]);
}
}
for(int j = 1; j <= m; ++j) {
Q[j].Clear();
for(int i = 1; i <= a; ++i)
Q[j].Push(h[i][j]);
}
ll sum = 0;
for(int i = a; i <= n; ++i) {
RQ.Clear();
for(int j = 1; j <= b; ++j)
RQ.Push(Q[j].Min());
for(int j = b; j <= m; ++j) {
sum += RQ.Min();
RQ.Pop();
if(j + 1 <= m)
RQ.Push(Q[j + 1].Min());
}
if(i + 1 <= n) {
for(int j = 1; j <= m; ++j) {
Q[j].Pop();
Q[j].Push(h[i + 1][j]);
}
}
}
printf("%lld\n", sum);
}