这几天也没有什么考场失分原因,全是打暴力,能打则打不打就挂这样。
T1 string
//很不幸,暴力都调了很久。
p.s.(可能只有本人才看得懂的->)sort里面是从a[i].l 到a[i].r+1 就是要注意这个+1…我试过了在各个地方+1/-1什么的才知道原来调用sort还有这些要注意的。
【solution写的是】
用线段树维护区间内 a~z 的个数,每次询问拆成 26 个区间修改操作。
还没理解透。所以也没写,而且看了标程和同学们的程序我觉得…真的不想写,太复杂太多细节了(线段树
T2 matrix
[10/10/2017 10:45am - 完了完了。 这个做法只有70分!!!改好后会传100分的!!!]
(可忽略,尊重出题人放在这里给与参考):
【solution写的是】
用 f[i][j]表示做到前 i 列,已经有 j 列在右侧区间放 1 的方案数。i
越过一个左侧区间的右端点时,从之前剩下的空列中选一列在这个左侧区间放 1。转移时分在右侧区间放 1或不放 1。
【看懂了请自动忽略我写的题解,乱七八糟的,我也没完全弄清楚,很懵b】
我应该不是世界上最后一个看不懂solution的人(吧),所以see一see我问的别人的思路。如果有任何错误的地方欢迎指出!!!【因为我也写的很虚】
(21:56pm : 好的写完题解我看懂solution了)
输入左端点a、右端点b。我们将l[a]++,r[b]++.
l[i]表示以第i列为左端点的有几行,r[i]表示以第i列为右端点的有几行。
题目要求每一行放两个1, 一个在左端点及左,一个在右端点及右。并且要求每一列最多只存在一个1。
和solution稍有不同,我们定义dp[i][j]表示当前考虑到第i列,右区间还剩j列没放1时的方案数。sum[i]记录r[i]前缀和。
从题目性质出发考虑,左区间和右区间互不影响,所以在这里是一个乘法原理,答案是左区间方案数*右区间方案数。
- 我们先来考虑右区间。这里我们分成(放1or不放1)两部分计算。
- 不放1:直接从上一列推过来。dp[i][j] = dp[i-1][j-r[i]] (r[i] <= j <= sum[i])
- 放1:dp[i-1][j] += dp[i][j] * j (1 <= j <= sum[i]) //放1和不放1符合的是加法原理
- 左区间。我们用res记录所有列-左区间的列数(也就是中间不用放1部分+右区间)。
- 此时dp[i][j] = dp[i][j] * (res-(sum[i]-j)) (0 <= j <= sum[i]) 做1~l[i]次。
- res我不知道怎么解释,口胡一下吧:你可以再开一个sumL[]记录l[]的前缀和,res = i-sumL[i]; 因为我们是遇到一个左端点就在之前的区间的空列里放1,所以就是这样。【真的口胡】
基本ok.
初始:dp[0][0] = 1;
结束:求dp[m][0];
改正确的程序如下:
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 3010;
const int p = 998244353;
int n, m, res;
int l[N], r[N], sum[N];
ll dp[N][N];
template <typename T>
T read(){
T N(0), F(1);
char C = getchar();
for(; !isdigit(C); C = getchar()) if(C == '-') F = -1;
for(; isdigit(C); C = getchar()) N = N*10 + C-48;
return N*F;
}
int main(){
freopen("b.in", "r", stdin);
freopen("b.out","w",stdout);
n = read<int>(); m = read<int>();
for(int i = 1; i <= n; i++){
int a, b;
a = read<int>(); b = read<int>();
l[a]++; r[b]++;
}
for(int i = 1; i <= m; i++) sum[i] = sum[i-1] + r[i];
dp[0][0] = 1;
for(int i = 1; i <= m; i++){
res++;
//右
for(int j = r[i]; j <= sum[i]; j++) dp[i][j] = dp[i-1][j-r[i]];//不放1
for(int j = 1; j <= sum[i]; j++) dp[i][j-1] += (dp[i][j] * (ll)j % (ll)p)%p;//放1
for(int k = 1; k <= l[i]; k++){
for(int j = 0; j <= sum[i]; j++) dp[i][j] = dp[i][j] * (ll)(max(0, res-sum[i]+j))%(ll)p;//左
res--;
}
}
printf("%lld\n", dp[m][0]);
return 0;
}
T3 big
【solution写的是】
对手做的是将 x 在二进制下左移一位。假设在异或 i 个数后左移,等价于开始时先左移,然后把前 i 个数左移一位。
问题转化为选一个数,使它左移一位后,与 m+1 个给定数分别异或的最小值最大。 将 m+1
个数建立一棵字典树,从上到下遍历来最大化结果:走到一个点时,如果往下只有 0,说明我们这一位取 1异或后只能是
1,累计结果中加上这一位的值,只有 1 也一样;如果既有 0 又有 1,说明这一位无论怎么取最后都是 0,分别往下走即可。
对我来说根本不可做啊啊啊!字典树都不会写。