考试复盘
首先先说 T 1 T1 T1,嗯,发现了列是相互独立的,所以分开考虑了
但是实在没想到线性基,就顺着自己的思路硬搞了 50 50 50跑路
老实说, 50 50 50分的部分分写得都是迷迷糊糊的,重构了好几遍
幸好过了,不然自己今天就是个圆溜的零光蛋了
没想到的点:
- 线性基
- 二进制的加法可以转化为异或
- bitset优化
再说 T 2 T2 T2,嗯——怎么说呢
想到了枚举最小值,然后线段树找符合的区间 [ v a l , v a l ∗ 2 ) [val,val*2) [val,val∗2)
然后想到了三维的过原点立方体
就不会了—— ,主要是之前没碰到过,只做过二维的可以用 s e t set set整活
这次遇到了,积累!!
幸好有 20 20 20的两种颜色数据
可是我还是没拿到
因为忘记减去 ( 0 , 0 , 0 ) (0,0,0) (0,0,0)三元组情况
发生这种过失性丢分的唯一原因就是没有好好分析样例
给自己敲个警钟!!!
没想到的点:
- 扫描线
- 线段树维护二维
注意的点:
- 仔细分析样例
最后说一下 T 3 T3 T3
其实很长一段时间是没有看懂题面的
样例也想不懂
是中午吃饭的时候去操场逛了两圈,才想懂了样例题面的意思
回来匆匆敲个暴力,还没过样例,害~
没想到的点:
- 后缀数组
- 同构字符串的转化——真的学到了!!
不过必须表扬自己认真磕出了样例
那为毛不磕一下 T 2 T2 T2的样例!!想穿越回去抽自己
说一下今天考试的自我感受
- 树立了自信心,因为整体来看自己对这套题目的正确性应该是有 50 % 50\% 50%的,省选并没有自己想象得那么恐怖
- 可以说这一场考试,是有史以来最认真的了,从头到尾都在努力思考,第一次写满了整整一张的思路推理验算,也发现其实在自己一步步的推演下,正解仿佛就只隔了一个教室而已,hhhh
- 真的跟着学长们一起,会有那种学习认真的氛围,而且今天确实学到了很多,也觉得他们和自己同班的同学都很强,自己也要继续努力!!!
matrix
在二进制下,加法等价于异或
∑ k = 1 n A [ i ] [ k ] × C [ k ] [ j ] \sum_{k=1}^nA[i][k]\times C[k][j] ∑k=1nA[i][k]×C[k][j]
∑ k ∈ S C [ k ] [ j ] = B [ i ] [ j ] × C [ i ] [ j ] \sum_{k∈S}C[k][j]=B[i][j]\times C[i][j] ∑k∈SC[k][j]=B[i][j]×C[i][j]
都是第 j j j列,说明每列是相互独立的,那么单独考虑每一列,统计答案时把所有列的方案数乘起来即可
若 B [ i ] [ j ] = 0 B[i][j]=0 B[i][j]=0,则等式右边为 0 0 0
若 B [ i ] [ j ] = 1 B[i][j]=1 B[i][j]=1,则等式右边为 C [ i ] [ j ] C[i][j] C[i][j]
因为是异或,所以两边都可以再异或上 C [ i ] [ j ] C[i][j] C[i][j]
∑ k ∈ S ′ C [ k ] [ j ] = 0 \sum_{k∈S'}C[k][j]=0 ∑k∈S′C[k][j]=0
**这里尚有问题,提醒自己一下
#include <cstdio>
#include <bitset>
using namespace std;
#define maxn 205
#define mod 998244353
int n, cnt, ans = 1;
int A[maxn][maxn], B[maxn][maxn];
bitset < maxn > lib[maxn], t;
int qkpow( int x, int y ) {
int res = 1;
while( y ) {
if( y & 1 ) res = 1ll * res * x % mod;
x = 1ll * x * x % mod;
y >>= 1;
}
return res;
}
int main() {
scanf( "%d", &n );
for( int i = 1;i <= n;i ++ )
for( int j = 1;j <= n;j ++ )
scanf( "%d", &A[i][j] );
for( int i = 1;i <= n;i ++ )
for( int j = 1;j <= n;j ++ )
scanf( "%d", &B[i][j] );
for( int j = 1;j <= n;j ++ ) {
for( int i = 1;i <= n;i ++ )
lib[i].reset();
cnt = 0;
for( int i = 1;i <= n;i ++ ) {
t.reset();
for( int k = 1;k <= n;k ++ )
if( A[i][k] ) t.flip( k );
if( B[i][j] ) t.flip( i );
for( int k = 1;k <= n;k ++ )
if( t[k] ) {
if( lib[k].any() ) t ^= lib[k];
else {
lib[k] = t;
cnt++;
break;
}
}
}
ans += n - cnt;
}
ans = qkpow( 2, ans );
printf( "%lld\n", ans );
return 0;
}
set
枚举所选集合中最小的权值, f o r for for循环扫一遍求出 [ v a l , v a l ∗ 2 ) [val,val*2) [val,val∗2) 里所有的物品,这些都是可选的
对于求出的数量用三元组表示 r , g , b r,g,b r,g,b 那么每一种颜色的选择都是 [ 0 , r ] , [ 0 , g ] , [ 0 , b ] [0,r],[0,g],[0,b] [0,r],[0,g],[0,b]
放在三维坐标里,就是求过原点的一个立方体的体积
考试的时候就只想到了立方体,发现自己只会平面的,所以这道题敲不来
用扫描线维护一维 r r r
具体来说就是对于三元组 ( r , g , b ) (r,g,b) (r,g,b) 在 r r r时刻将 ( g , b ) (g,b) (g,b)插进去
从大到小计算
因为在 r r r时刻的 ( g , b ) (g,b) (g,b)二元组,一定在 r − 1 r-1 r−1时刻也是正确的
从小到大,有的二元组会涉及到删除,对于蒟蒻来说不好做,因为我扫描线就很困难了好吧
剩下两维的平面直角坐标系计算围成的面积,以前做过这种题
用 s e t set set或者线段树都可以做
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 500005
struct node {
int val, c;
node(){
}
node( int Val, int C ) {
val = Val, c = C;
}
}v[maxn];
vector < pair < int, int > > G[maxn];
int n;
int tot[3];
long long tree[maxn << 2], tag[maxn << 2], maxx[maxn << 2];
bool cmp( node x, node y ) {
return x.val < y.val;
}
int id( char c ) {
if( c == 'R' ) return 0;
if( c == 'G' ) return 1;
if( c == 'B' ) return 2;
}
void pushdown( int num, int l, int r ) {
if( tag[num] ) {
maxx[num << 1] = tag[num << 1] = tag[num];
maxx[num << 1 |1] = tag[num << 1 | 1] = tag[num];
int mid = ( l + r ) >> 1;
tree[num << 1] = tag[num] * ( mid - l + 1 );
tree[num << 1 | 1] = tag[num] * ( r - mid );
tag[num] = 0;
}
}
void modify( int num, int l, int r, int L, int R, int val ) {
if( L <= l && r <= R && maxx[num] < val ) {
tree[num] = 1ll * val * ( r - l + 1 );
maxx[num] = tag[num] = val;
return;
}
if( l == r ) return;
int mid = ( l + r ) >> 1;
pushdown( num, l, r );
if( L <= mid && maxx[num << 1 | 1] < val )//如果是被完全包含的面积是不进行计算的 这里可以多想想
modify( num << 1, l, mid, L, R, val );
if( mid < R )
modify( num << 1 | 1, mid + 1, r, L, R, val );
tree[num] = tree[num << 1] + tree[num << 1 | 1];
maxx[num] = max( maxx[num << 1], maxx[num << 1 | 1] );
}
int main() {
scanf( "%d", &n );
int x; char color[3];
for( int i = 1;i <= n;i ++ ) {
scanf( "%d %s", &x, color );
v[i] = node( x, id( color[0] ) );
}
sort( v + 1, v + n + 1, cmp );
G[0].push_back( make_pair( 0, 0 ) );
int j = 1;
for( int i = 1;i <= n;i ++ ) {
while( j <= n && v[j].val < ( v[i].val << 1 ) )
tot[v[j].c] ++, j ++;
G[tot[0]].push_back( make_pair( tot[1], tot[2] ) );
tot[v[i].c] --;
}
long long ans = 0;
for( int i = n;~ i;i -- ) {
//扫描线去掉一维 相当于切立方体的厚度 切成厚度为1的多个面状
for( int j = 0;j < G[i].size();j ++ )
modify( 1, 0, n, 0, G[i][j].first, G[i][j].second + 1 ); //加1是因为这个面积是和原点相围成的 画画图会发现其实需要列拔高一层
ans += tree[1];
}
printf( "%lld", ans - 1 );//不算(0,0,0)
return 0;
}
string
对于一个字符串,将第一次新出现的字符设为 0 0 0,其余位置赋值为该位置减去上一次字符出现的位置
这样的转化就将同构字符串改写成完全相同的数字串了
这里真的太巧妙了,第一次遇到,记下来!!!
求本质不同的子串就是后缀数组的基础了
所有子串长度 − - − h e i g h t height height数组之和
但是!!
题目是处理子串之间的问题
那么就有子串的改写会与原串的改写有出入的问题
就是说子串中第一次出现的某个字符不一定是原串整个串中第一次出现
那么子串该位置就需要修改为 0 0 0
一共只有 26 26 26个字符,所以子串最多只会有 26 26 26个位置与原串不一样
可以人为暴力排序
将一段根据修改的 0 0 0划分成若干段
两个子串的 c m p cmp cmp比较,如果是相同的部分就用原串的后缀数组 s a sa sa搞定,更改过的地方就暴力比较
总结一下,代码实现思路
- 对原串改写
- 对原串后缀数组
- 利用原串的后缀数组再次暴力排序后缀
- 求出按照新规定下排好的后缀之间的 l c p lcp lcp之和
- 子串总长度减去 l c p lcp lcp之和
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 100005
struct node {
int n, m;
int x[maxn], id[maxn], rnk[maxn << 1], h[maxn], sa[maxn], tot[maxn];
int st[maxn][20];
void suffix( int N, int *s ) {
n = N, m = N + 1;
for( int i = 1;i <= n;i ++ ) tot[x[i] = s[i]] ++;
for( int i = 1;i <= m;i ++ ) tot[i] += tot[i - 1];
for( int i = n;i;i -- ) sa[tot[x[i]] --] = i;
for( int k = 1;k <= n;k <<= 1 ) {
int num = 0;
for( int i = n - k + 1;i <= n;i ++ ) id[++ num] = i;
for( int i = 1;i <= n;i ++ ) if( sa[i] > k ) id[++ num] = sa[i] - k;
memset( tot, 0, sizeof( tot ) );
for( int i = 1;i <= n;i ++ ) tot[x[i]] ++;
for( int i = 1;i <= m;i ++ ) tot[i] += tot[i - 1];
for( int i = n;i;i -- ) sa[tot[x[id[i]]] --] = id[i];
for( int i = 1;i <= n;i ++ ) rnk[i] = x[i];
x[sa[1]] = num = 1;
for( int i = 2;i <= n;i ++ )
x[sa[i]] = ( rnk[sa[i]] == rnk[sa[i - 1]] && rnk[sa[i] + k] == rnk[sa[i - 1] + k] ) ? num : ++ num;
if( n == num ) break;
m = num;
}
for( int i = 1;i <= n;i ++ ) rnk[sa[i]] = i;
int k = 0;
for( int i = 1;i <= n;i ++ ) {
if( rnk[i] == 1 ) continue;
if( k ) k --;
int j = sa[rnk[i] - 1];
while( i + k <= n && j + k <= n && s[i + k] == s[j + k] ) k ++;
h[rnk[i]] = k;
}
for( int i = 1;i <= n;i ++ ) st[i][0] = h[i];
for( int j = 1;j < 20;j ++ )
for( int i = 1;i <= n;i ++ )
if( i + ( 1 << j - 1 ) > n ) break;
else st[i][j] = min( st[i][j - 1], st[i + ( 1 << j - 1 )][j - 1] );
}
int lcp( int l, int r ) {
if( l <= 0 || r <= 0 || l > n || r > n ) return 0;
if( l == r ) return n - l + 1;
l = rnk[l], r = rnk[r];
if( l > r ) swap( l, r );
l ++;
int i = log( r - l + 1 ) / log( 2 );
return min( st[l][i], st[r - ( 1 << i ) + 1][i] );
}
}SA;
int n;
char s[maxn];
int last[maxn], idx[maxn], t[maxn];
int nxt[maxn][30];
int LCP( int x, int y ) {
int sx = x, sy = y;
x --, y --;
int len = 0;
for( int i = 0;i < 26 && x + 1 <= n && y + 1 <= n;i ++ ) {
int l = SA.lcp( x + 1, y + 1 );
if( x + l + 1 < nxt[sx][i] && y + l + 1 < nxt[sy][i] )
return len + l;
if( nxt[sx][i] - sx == nxt[sy][i] - sy ) {
//如果一段都是相同 就一段段的跳
len += nxt[sx][i] - x;
x = nxt[sx][i], y = nxt[sy][i];
}
else {
if( nxt[sx][i] - sx < nxt[sy][i] - sy )
return len + nxt[sx][i] - x - 1;
else
return len + nxt[sy][i] - y - 1;
}
}
return len;
}
bool cmp( int x, int y ) {
int len = LCP( x, y );
if( x + len > n || y + len > n ) return x + len > n;
int vx = t[x + len], vy = t[y + len];
for( int i = 0;i < 26;i ++ )
if( nxt[x][i] == x + len ) {
vx = 0; break; }
for( int i = 0;i < 26;i ++ )
if( nxt[y][i] == y + len ) {
vy = 0; break; }
return vx < vy;
}
int main() {
scanf( "%d %s", &n, s + 1 );
for( int i = 1;i <= n;i ++ ) {
int j = s[i] - 'a';
if( ! last[j] ) t[i] = n + 1;
else t[i] = i - last[j];
last[j] = i, idx[i] = i;
}
SA.suffix( n, t );
for( int i = 0;i < 26;i ++ ) nxt[n + 1][i] = n + 2;
for( int i = n;i;i -- ) {
for( int j = 0;j < 26;j ++ )
nxt[i][j] = nxt[i + 1][j];
nxt[i][s[i] - 'a'] = i;
}
for( int i = 1;i <= n;i ++ )
sort( nxt[i], nxt[i] + 26 );
sort( idx + 1, idx + n + 1, cmp );
long long ans = 1ll * n * ( n + 1 ) / 2;
for( int i = 1;i < n;i ++ )
ans -= LCP( idx[i], idx[i + 1] );
printf( "%lld\n", ans );
return 0;
}
后记:大概会在周末进行再次复盘,重新消化吸收,顺便看看到时候自己是否会将好不容易转操场想懂的地方又搞懵
走了回家睡觉,现在时间2021/3/15 22:48