模拟测试20190707 [排序//划艇//放棋子]
苟蒻没有什么学问,但还是能爆个零的
注:灰色背景为本人自说自话,可跳过
T1:「SDOI2015」排序(链接)
[一道考场上连暴力都没打出来的题,再次提醒了我看题的重要性(看成了每种操作可以进行无数次真是对不起了啊)]
我们手模几个点就会发现如果一种序列成立,那么他的全排列一定成立
则我们每种序列只需要搜一次,然后就可以把它的阶乘累加进答案
则不需管顺序,可以直接从小操作到大操作搜索
我们观察发现小的操作进行的区间一定被大的包含,则小的操作进行之后这个小区间的顺序一定不变
那我们在搜索的时候,判定当前序列在下一个操作下是否连续,以及有几个不连续
若没有,则不需进行操作,直接向下搜索
若有一个,对这个区间进行交换并向下搜索
若有两个,则一共有4种交换方法,枚举判断是否可行并向下搜索
复杂度O(4n)期望得分100分
#include <iostream>
#include <cstdio>
const int mod = 1e9 + 9;
#define ll long long
using namespace std;
int a[5000], n, jie[15] = { 1 }, ans, t[15] = { 1 }, is[15];
inline bool judge(int x, int y) {
for (int i = 1; i < t[x]; i++)
if (a[y + i] != a[y + i - 1] + 1)
return 1;
return 0;
}
inline void swaps(int x, int y, int z) {
for (int i = 0; i < t[z]; i++) swap(a[x + i], a[y + i]);
}
void dfs(int x, int num) {
if (x == n) {
ans += jie[num];
return;
}
int tt = 0, ttt = 0;
for (int i = 1; i <= t[n]; i += t[x + 1])
if (judge(x + 1, i)) {
if (!tt)
tt = i;
else if (!ttt)
ttt = i;
else
return;
}
if (!tt)
dfs(x + 1, num);
else if (!ttt) {
swaps(tt, tt + t[x], x);
dfs(x + 1, num + 1);
swaps(tt, tt + t[x], x);
} else {
for (int i = 0; i <= 1; i++)
for (int j = 0; j <= 1; j++) {
swaps(tt + i * t[x], ttt + j * t[x], x);
if (!judge(x + 1, tt) && !judge(x + 1, ttt))
dfs(x + 1, num + 1);
swaps(tt + i * t[x], ttt + j * t[x], x);
}
}
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= (1 << n); i++) scanf("%d", &a[i]);
for (int i = 1; i <= n; i++) jie[i] = jie[i - 1] * i, t[i] = t[i - 1] << 1;
dfs(0, 0);
printf("%d", ans);
}
T2:「APIO2016」划艇(链接)
[考试的时候想出来一个暴力,用树状数组优化了一下,但并没有注意到子任务这个玩意,而数组又开不了那么大,直接爆零
旁边cbx非常机智地使用了动态开点线段树(下面会讲),但是因为线段树没有开4倍只得了9分(鼓掌)]
9分算法:
第一个子任务保证a[i]==b[i],则可以用一个类似最长上升子序列的dp
dp[i]=∑dp[j] (a[j]<a[i]),
复杂度O(n2)期望得分9分
31分算法:
设dp[i][j],表示在第i所学校派出了j艘划艇,则dp[i][j]=1+∑k∑tdp[k][t](1<=k<=i-1,1<=t<=j-1)
我们发现这个式子后面很明显是一个前缀和,则式子可以变成dp[i][j]=sum[j-1]
j很大,为了不开数组,前缀和我们考虑用数据结构维护,但是不能用树状数组,因为数组开不了
权值线段树是个好东西,而且我们发现连dp数组都不用开,直接add(get(j-1),j)就好了
最开始给0节点+1,最后输出get(max)-1
复杂度O(nlogmax)期望得分31分
#include <iostream>
#include <cstdio>
const int mod = 1e9 + 7;
#define ll long long
using namespace std;
int a[510], b[510], n, ma;
int da[20000000], ls[20000000], rs[20000000], cnt;
inline int q(int x) {
if (x >= mod)
x -= mod;
if (x < 0)
x += mod;
return x;
}
void insert(int &x, int y, int l, int r, int is) {
if (!x)
x = ++cnt;
if (l == r) {
da[x] = q(da[x] + y);
return;
}
int mid = l + r >> 1;
if (is <= mid)
insert(ls[x], y, l, mid, is);
else
insert(rs[x], y, mid + 1, r, is);
da[x] = q(da[ls[x]] + da[rs[x]]);
}
int get(int x, int y, int L, int R) {
if (!x)
return 0;
int mid = L + R >> 1, ans = 0;
if (R <= y)
return da[x];
ans = (ans + get(ls[x], y, L, mid)) % mod;
if (y > mid)
ans = (ans + get(rs[x], y, mid + 1, R)) % mod;
return ans;
}
inline int Max(int x, int y) { return x < y ? y : x; }
inline int Min(int x, int y) { return x < y ? x : y; }
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d%d", &a[i], &b[i]), ma = Max(ma, b[i]);
int ans, root = 0;
insert(root, 1, 0, ma, 0);
for (int i = 1; i <= n; i++) {
for (int j = b[i]; j >= a[i]; j--) insert(root, get(root, j - 1, 0, ma), 0, ma, j);
}
printf("%d", q(get(root, ma, 0, ma) - 1));
}
100分算法:
首先是一个我们以前没有接触过的东西——区间离散化
把区间的左右端点读入,sort并去重,剩下了cnt个数,就得到了cnt-1个区间
记录每个区间的长度,把原来的a[i],b[i],换成在这个数组中的位置+1
好了,我们成功把1e9的区间变成了至多1000个小区间,那么这有什么用呢
设dp[i][j]表示第i所学校选择派出第j个区间里某个数的划艇
对于比j小的区间,显然dp[i][j]+=∑∑dp[k][t](1<=k<=i-1,1<=t<=j-1)
那对于j这个区间怎么办呢
我们来解决这样一个问题:给你x个相同的区间,每个区间你可以选择选或不选,在你选出的所有区间中,每个取出一个数,并且取出的数严格递增
考虑一个小数据,在3个区间里取数,发现总数为C(3,0)*C(L,0)+C(3,1)*C(L,1)+C(3,2)*C(L,2)+C(3,3)*C(L,3)=C(L+3,3)
扩展一下就得到了C(L+x,x)
回到我们的问题,给你x个相同的区间,每个区间你可以选择选或不选,至少选两个,在你选出的所有区间中,每个取出一个数,并且取出的数严格递增
还是考虑3个区间,总数为C(3,2)*C(L,2)+C(3,3)*C(L,3)=C(L+1,3)=C(L+3-2,3)
扩展一下得到C(L+x-2,x)
则对于有交集的区间,dp[i][j]+=∑m(num+L-2,num)×∑k∑tdp[k][t](1<=k<=m-1,1<=t<=j-1,num表示m到i有几个包含j的数)
这个dp看起来复杂度O(n5)不可做,但我们发现和31分算法一样,这里出现了两个从1开始的∑,其实就是矩阵的一个角
而且我们发现单点状态对以后没有什么用,则dp数组可以直接维护前缀和
那么dp就变成了dp[i][j]+=/dp[i-1][j-1]*L[j]
\∑mC(num+L-2,num)×dp[m-1][j-1](num表示m到i有几个包含j的数)
每个学校处理完了之后再维护成前缀和就好了
复杂度O(n3)期望得分100
#define ll long long
#include <iostream>
#include <cstdio>
#include <set>
#include <algorithm>
const int mod = 1e9 + 7;
using namespace std;
int a[510], b[510], f[1010], cnt = 0, L[1010], dp[510][1010], ni[1010];
set<int> has;
int main() {
int n;
scanf("%d", &n);
ni[1] = 1;
for (int i = 2; i <= 500; i++) ni[i] = 1ll * (mod - mod / i) * ni[mod % i] % mod;
for (int i = 1; i <= n; i++) {
scanf("%d%d", &a[i], &b[i]);
has.insert(a[i]);
has.insert(b[i] + 1);
}
for (set<int>::iterator it = has.begin(); it != has.end(); it++) f[++cnt] = *it;
for (int i = 2; i <= cnt; i++) L[i] = f[i] - f[i - 1];
for (int i = 1; i <= n; i++) {
a[i] = upper_bound(f+1, f + cnt + 1, a[i]) - f;
b[i] = upper_bound(f+1, f + cnt + 1, b[i]) - f;
// cout<<a[i]<<" "<<b[i]<<endl;
}
for (int i = 1; i <= cnt; i++) dp[0][i] = 1;
for (int i = 1; i <= n; i++) {
dp[i][1] = 1;
for (int j = a[i]; j <= b[i]; j++) {
dp[i][j] = (ll)dp[i - 1][j - 1] * L[j] % mod;
int can = 1;
ll c = L[j] - 1;
for (int k = i - 1; k; k--)
if (b[k] >= j && a[k] <= j) {
can++;
c = c * (L[j] + can - 2) % mod * ni[can] % mod;
if (!c)
break;
dp[i][j] = ((ll)dp[i][j] + (ll)c * dp[k - 1][j - 1] % mod) % mod;
}
}
for (int j = 2; j <= cnt; j++)
dp[i][j] = ((ll)dp[i][j] + dp[i][j - 1] + dp[i - 1][j] - dp[i - 1][j - 1] + mod) % mod;
}
printf("%lld\n", ((ll)dp[n][cnt] - 1 + mod) % mod);
return 0;
}
T3:「CQOI2011」放棋子(链接)
[一道在考场上想出正解的题,但是因为一部分不会处理只拿到30分]
dp很好想,我们发现当前几种棋子放完之后,下一种棋子能放当且仅当剩下了完全空的几行几列,而且行列的位置没有影响
则我们设dp[i][j][k]表示放第i种棋子,剩下了j行k列,g[i][j][k]表示用第i种棋子放满i×j的矩阵(每行每列都有棋子)的方案数
dp[i][j][k]=∑p∑qdp[i-1][p][q]*g[p-j][q-k][i]×C(p,j)*C(q,k)(j<=p<=n,k<=q<=m)
然后我们考虑g怎么处理,发现直接求出很麻烦,考虑容斥
g[i][j][k]=C(i*j,num[k])-∑p∑qg[p][q][k](0<=p<=i,0<=q<=j)
预处理或者一边dp一边处理都行
复杂度O(cn4)期望得分100分
#include <iostream>
#include <cstdio>
const int mod = 1e9 + 9;
#define ll long long
using namespace std;
int C[1000][1000], num[12], f[12][50][50], g[50][50][15];
inline int md(int x) {
if (x >= mod)
x -= mod;
return x;
}
int main() {
int n, m, c, sum = 0;
scanf("%d%d%d", &n, &m, &c);
for (int i = 1; i <= c; i++) scanf("%d", &num[i]), sum += num[i];
C[0][0] = 1;
for (int i = 1; i <= n * m; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++) C[i][j] = md(C[i - 1][j] + C[i - 1][j - 1]);
}
if (c == 1) {
printf("%d", C[n * m][num[1]]);
return 0;
}
f[0][n][m] = 1;
for (int i = 1; i <= c; i++)
for (int j = 0; j <= n; j++)
for (int k = 0; k <= m; k++)
if (j * k >= num[i]) {
g[j][k][i] = C[k * j][num[i]];
for (int p = 0; p <= j; p++)
for (int q = 0; q <= k; q++)
if (p < j || q < k)
g[j][k][i] = md(1ll * g[j][k][i] -
1ll * C[j][p] * C[k][q] % mod * g[p][q][i] % mod + mod);
}
for (int i = 1; i <= c; i++)
for (int j = 0; j <= n; j++)
for (int k = 0; k <= m; k++)
for (int p = j; p <= n; p++)
for (int q = k; q <= m; q++)
f[i][j][k] = md(1ll * f[i][j][k] + 1ll * f[i - 1][p][q] * g[(p - j)][(q - k)][i] %
mod * C[p][j] % mod * C[q][k] % mod);
ll ans = 0;
for (int i = 0; i <= n; i++)
for (int j = 0; j <= m; j++) ans = md(ans + f[c][i][j]);
printf("%lld", ans);
}
emmm......聊聊这次考试的经历,刚开始用了将近1h打第一题的暴力,没看题所以没打出来
然后专攻第二题,打了个树状数组,没注意子任务,Wa 0
最后看的第三题,发现dp非常水,然后非常开心得码了出来,但是两个变量名写错了,再加g数组没处理出来Wa 10
简直苟蒻标配经历&分数
以后加油吧