CSP-S模拟11
T1.回文
传纸条——坐标dp。对于学过坐标dp的人来说应该是签到题吧。把回文抽象成两个人分别从\((1, 1), (n, m)\)出发,走路径相同的方案数。直接定义\(dp[i][j][s][t]\)为第一个人在(i, j),第二个人在(s, t),显然转移要保证\(i+j-2=n+m-s-t\),即步数相等。最后合并答案要分奇数和偶数考虑。
另外,这题的模数非同寻常\(993244853\),而且比较卡内存。
代码
#define sandom signed
#include <cstdio>
#include <iostream>
#define re register int
using namespace std; typedef long long ll;
const int Z = 505; const int mod = 993244853;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
int n, m;
char a[Z][Z];
int dp[Z][Z][Z];
long long ans;
sandom main()
{
n = read(), m = read();
for (re i = 1; i <= n; i++) scanf("%s", a[i] + 1);
if (a[1][1] == a[n][m]) dp[1][1][n] = 1;
else { puts("0"); return 0; }
for (re i = 1; i <= n; i++)
for (re j = 1; j <= m; j++)
for (re s = n; s >= i; s--)
{
int t = n + m + 2 - i - j - s;
if (t <= 0 || t > m) continue;
ans = dp[i][j][s];
if (a[i][j] == a[s][t])
ans = ans + dp[i - 1][j][s + 1] + dp[i - 1][j][s] + dp[i][j - 1][s + 1] + dp[i][j - 1][s];
dp[i][j][s] = ans % mod;
}
ans = 0;
for (re i = 1; i <= n; i++)
for (re j = 1; j <= m; j++)
{
int s = i, t = j;
if (i + j + s + t == n + m + 2) ans += dp[i][j][s];
s = i + 1, t = j;
if (i + j + s + t == n + m + 2) ans += dp[i][j][s];
s = i, t = j + 1;
if (i + j + s + t == n + m + 2) ans += dp[i][j][s];
ans %= mod;
}
cout << ans;
return 0;
}
T2.快速排序
题面给出了伪代码,下发文件给出了具体代码,但全是指针啊,所以于是自己照着伪代码打了快排。有一说一,打完暴力就溜了……最后二十分钟回来看这个题,发现这不是水题吗……对序列扫一遍:如果是\("nan"\),则会认为后面所有的数都大于它,直接跳过;如果是一个数,则会认为后面所有的\("nan"\)都大于它,只需要把后面所有比它小的数都提到前面,用优先队列和vs标记来实现。
代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
using namespace std; int wrt[20], TP;
const int Z = 5e5 + 100; typedef long long ll;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline void write(int x) { if (!x) { printf("%s ", "nan"); return; } TP = 0; while (x >= 10) wrt[++TP] = x % 10, x /= 10; wrt[++TP] = x; while (TP) putchar(wrt[TP--] | 48); putchar(' '); }
inline int max(int a, int b) { return a > b ? a : b; } inline int min(int a, int b) { return a < b ? a : b; }
int n, m, ans;
char ch[Z];
int a[Z], b[Z];
inline int getdigit(char s[])
{
if (s[1] == 'n') return 0;
int len = strlen(s + 1), x = 0;
for (re i = 1; i <= len; i++) x = (x << 1) + (x << 3) + (s[i] ^ 48);
return x;
}
priority_queue < int, vector<int>, greater<int> > q;
int t, mx;
void qsort(int l, int r)
{
if (l >= r) { b[++t] = a[l]; return; }
if (!a[l]) b[++t] = 0;
else if (a[l] >= mx)
{
while (!q.empty() && q.top() < a[l]) b[++t] = q.top(), q.pop();
b[++t] = q.top(), q.pop(); mx = a[l];
}
qsort(l + 1, r);
}
sandom main()
{
int T = read();
while (T--)
{
n = read(); t = 0, mx = 0;
for (re i = 1; i <= n; i++) scanf("%s", ch + 1), a[i] = getdigit(ch);
for (re i = 1; i <= n; i++) if (a[i]) q.push(a[i]);
qsort(1, n);
for (re i = 1; i <= n; i++) write(b[i]); putchar('\n');
while (!q.empty()) q.pop();
}
return 0;
}
T3.混乱邪恶
这题暴力和假贪心满分了?震惊!!!这题是从去年数学高中联赛二试T4改编来的。所以证明过程显然是一道数学题。
首先,若\(n\)为奇数,在a中加入一个\(0\),这样我们只需要讨论\(n\)为偶数的情况。将a排序,构造差分数组\(d_i=a_{2i}-a_{2i-1}\),这样我们直接假贪心就可以过了。我们对每一个\(d\)分配一个\(e\),使得\(\sum d_ie_i=0\),这时候聪明的小朋友会发现这跟我直接求一个原式有甚么区别。这就是这道题构造的重点,首先抛出一个结论\(\sum d_i <= m-\frac{n}{2} < n\)。现在来证明:
1.因为\(a\)互不相同,所以对于每一个\(d\),大小至少为1,也就是说有一个\(>=\frac{n}{2}\)的下界,考虑扩大到n,根据抽屉原理,只要能够保证每一个\(d\)至少为2即可,也就是说每两个\(a\)之间有一个空隙,所以至少要满足\(m>=2n\),但由于题干给出了\(n\)的范围\(( \lfloor \frac{2m}{3} \rfloor, m]\),得知此情况不可能。所以一定小于\(n\)
2.把每一个\(d\)抽象为一段左开右闭的线段,\(\sum d\)就是线段覆盖的面积,考虑到至少有\(\frac{n}{2}\)个左端点,以及剩下的空隙,所以被覆盖的一定\(<=m-\frac{n}{2}\)。(这是借鉴的La_Pluma大佬的证明)
构造:因为\(\sum a_i\)为偶数,所以\(\sum d_i\)也为偶数,并且小于\(n\)。简化问题:\(n\)个整数\(d_i\),和为偶数且小于\(2n\),一定存在\(e\)的构造方案。又到了我们熟悉的数学归纳法:当\(n=1\)时,和只能为\(0\),这是边界;当所有\(d\)都相等时,只需要\(+1\) \(-1\)交替即可。我们把d排序,每次取出\(max\)和\(min\),把它们删除,并加入\(max-min\),这时综合减少了\(2min\),因为\(min\)至少为1,所以问题就转变成了\(n-1\)个数,和为偶数且小于\(2(n-1)\),发现问题可以递归处理,且规定了边界。为什么取最大和最小值:存在两个\(d\)相等的情况,这时我们会加入一个数值为\(0\)的元素,这将导致\(min\)为零,构造产生错误;如果\(max\)与\(min\)相同,则回到了我们的边界。
我们把每次取出和插入的过程构建成一棵二叉树,在每条边上赋上1或-1的权值,表示这个数是由另外两个数一个取+,一个取-得到的。我们从递归边界返回时,如果是+,直接下传;如果是-,异或一下。当到达\(a\)底层时,得到\(c\)。
这题的构造一定有解,所以无解的输出会爆。
代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
using namespace std; int wrt[20], TP;
const int Z = 5e6 + 10;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline void write(int x) { TP = 0; if (x < 0) putchar('-'), x = -x; while (x >= 10) wrt[++TP] = x % 10, x /= 10; wrt[++TP] = x; while (TP) putchar(wrt[TP--] | 48); putchar(' '); }
int n, m, k, ans;
int a[Z], c[Z], p[Z];
struct moj
{
int id, val;
pair <int, int> frm;
friend bool operator <(moj A, moj B) { return A.val < B.val; }
}; moj nod[Z];
multiset <moj> s;
void solve(int n)
{
if (n == 1) return;//边界条件,此时和一定为0
auto t1 = s.begin(), t2 = --s.end();
moj Min = *t1, Max = *t2;//把最大值与最小值做差
s.erase(t1), s.erase(t2);
nod[m] = {m, Max.val - Min.val, make_pair(Max.id, Min.id)};
s.insert(nod[m++]);//新条件
solve(n - 1);//继续递归
}
void dfs(int rt, int num)
{
if (nod[rt].frm.first <= n)//到底了,给出答案
{
c[nod[rt].frm.first] = num ? -1 : 1;
c[nod[rt].frm.second] = num ? 1 : -1;
return;
}
dfs(nod[rt].frm.first, num);
dfs(nod[rt].frm.second, num ^ 1);//因为做了减法,需要取反
}
sandom main()
{
n = read(), m = read() + 1;
for (re i = 1; i <= n; i++) a[i] = read();
if (n & 1) a[++n] = 0;
for (re i = 1; i <= n; i++) p[a[i]] = i;
sort(a + 1, a + 1 + n);
for (re i = 2; i <= n; i += 2)
{
nod[m] = {m, a[i] - a[i - 1], make_pair(p[a[i]], p[a[i - 1]])};
s.insert(nod[m++]);
}
solve(n / 2); dfs(m - 1, 0);
puts("NP-Hard solved");
if (a[1] == 0) for (re i = 1; i < n; i++) write(c[i]);
else for (re i = 1; i <= n; i++) write(c[i]);
return 0;
}
T4.校门外歪脖树上的鸽子
直接莽一棵不平衡的线段树。如题,我鸽了。
一些TIP:linux下比对文件一定要忽略空格!!!交代码直接删除调试信息,一定要编译运行一次。行末空格蒙蔽了我的双眼,我竟以为T2自己的正解挂掉了,并且惊奇的发现我的std也是错的,所以正解我连交都没交……