ZROI 2019 暑期游记
ZROI 游记
在自闭中度过了17天
挖了无数坑,填了一点坑
所以还是有好多坑啊zblzbl
挖坑总集:
- 时间分治
- 差分约束
- Prufer序列
- 容斥
- 树上数据结构 例题C (和后面的例题)
- 点分
- 最大流、费用流、上下界
- Hero meet devil(dp套dp)
- Pollards’ Rho
- CRT & exCRT
- BSGS & exBSGS
- NTT & FFT 以及 分治NTT & FFT (& 原根)
- Cipolla 算法(二次剩余)
- Min25
ZROI D1
T1 小K与集合
首先知道,如果有多于k个1,显然直接把k个1分别放到不同的组,然后剩下的随便放,第一回合就没法拿了。
那么如果只有k-1个1呢,稍微想一下知道剩下那个组至少必须放k个2。因为就算拿到了2的组,会得到k个1。
然后感性想一想,k个i可以当一个i-1。没错,这题这么贪心是对的。
证明咕咕咕了。
这个玩意实现起来有点麻烦,我写的是递归。用ned(x,w)表示需要拿1个x到w组,如果没有x了递归到进行k次ned(x-1,w)。记忆化一下是不是这个数字已经凑不出来了。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define MAXN 100006
int n , k;
int A[MAXN] , cnt[MAXN];
int ksm( int a , int x ) {
long long ans = 1 , cur = a;
while( x ) {
if( x & 1 ) ans *= cur;
cur *= cur , x >>= 1;
if( ans > n || cur > n ) return 0x3f3f3f3f;
}
return ans;
}
vector<int> group[MAXN];
int ok[MAXN] , vis[MAXN];
bool ned( int x , int w ) {
if( x == n + 1 ) return ok[x] = false;
if( ~ok[x] ) return ok[x];
if( cnt[x] ) { group[w].push_back( x ) , --cnt[x]; return true; }
int r = k; while( r-- ) if( !ned( x + 1 , w ) ) return ok[x] = false;
return true;
}
vector<int> pos[MAXN];int t[MAXN];
int main() {
int T;cin >> T;
while( T-- ) {
cin >> n >> k;
memset( ok , -1 , sizeof ok );
memset( vis , 0 , sizeof vis );
memset( cnt , 0 , sizeof cnt );
memset( t , 0 , sizeof t );
for( int i = 1 ; i <= n ; ++ i ) pos[i].clear();
for( int i = 1 ; i <= n ; ++ i ) scanf("%d",&A[i]) , ++cnt[A[i]] , pos[A[i]].push_back( i );
for( int i = 0 ; i < k ; ++ i ) group[i].clear();
int flg = 0;
int r = k; while( r-- ) {
if( !ned( 1 , r ) )
{ puts( "0" ); flg = 1 ; break; }
}
if( flg ) continue;
int poi = 0;
printf("1");
for( int i = 0 ; i < k ; ++ i ) {
puts("");
printf("%d ",i != k - 1 ? group[i].size() : n - poi);
poi += group[i].size();
for( int j = 0 ; j < group[i].size() ; ++ j )
printf("%d ",pos[group[i][j]][t[group[i][j]]]) , vis[pos[group[i][j]][t[group[i][j]]]] = 1 , ++ t[group[i][j]];
}
for( int i = 1 ; i <= n ; ++ i ) if( !vis[i] )
printf("%d ",i);
puts("");
}
}
T2 小K的数据
直接暴力往前跳,没了。
考虑(l2,r2) 作为儿子节点 (l1,r1)作为父亲节点建树,然后每次从一个点往上跳就没了(当然跳要用模运算优化成O1)。
(这里的区间指(l2,r2))
然而每次二分在哪个区间,然后跳出去,最少跳出一个区间,最多一询问跳m2次,复杂度是对的。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
using namespace std;
#define MAXN 1006
int n , m1 , m2 , q;
int pos[MAXN] , c[MAXN];
struct que{
int l1 , r1 , l2 , r2;
que( int a = 0 , int b = 0 , int c = 0 , int d = 0 ) :l1(a),r1(b),l2(c),r2(d) {}
bool operator < ( const que a ) const {
return l2 < a.l2;
}
}Q[MAXN];
map<int,int> col;
int main() {
cin >> n >> m1 >> m2 >> q;
char ch[4];
for( int i = 1 ; i <= m1 ; ++ i ) scanf("%d%s",&pos[i] , ch ) , c[i] = ch[0];
for( int i = 1 ; i <= m2 ; ++ i ) scanf("%d%d%d%d",&Q[i].l1 , &Q[i].r1 , &Q[i].l2 , &Q[i].r2);
sort( Q + 1 , Q + m2 + 1 );
for( int i = 1 ; i <= m1 ; ++ i ) {
int poi = pos[i] , p;
while( true ) {
p = upper_bound( Q + 1 , Q + m2 + 1 , que( 0 , 0 , poi , 0 ) ) - Q ;
if( p == 1 ) break;
-- p;
int l = Q[p].l1 , r = Q[p].r1 , L = Q[p].l2 , R = Q[p].r2;
if( poi < L || poi > R ) break;
poi = ( poi - l ) % ( L - l ) + l;
}
col[poi] = c[i];
}
int x;
while( q --> 0 ) {
scanf("%d",&x);
int p;
while( true ) {
p = upper_bound( Q + 1 , Q + m2 + 1 , que( 0 , 0 , x , 0 ) ) - Q ;
if( p == 1 ) break;
-- p;
int l = Q[p].l1 , r = Q[p].r1 , L = Q[p].l2 , R = Q[p].r2;
if( x < L || x > R ) break;
x = ( x - l ) % ( L - l ) + l;
}
printf("%c\n" , col[x] ? col[x] : '?');
}
}
T3 小K与奇数
首先判断无解,如果有度数不是奇数 或 边数不是偶数 是 无解 的充要条件。(证明可以看神仙duyi的博客)
考虑没有限制长度是偶数的情况
考虑建立一个虚拟节点 n+1 连向全部的点,由于所有点度数是奇数并且题目保证了点数是偶数,一定存在一条欧拉回路(所有点度数都是偶数了)
把欧拉回路跑出来,然后通过n+1分割出的很多序列就是答案。
那么如果长度是偶数呢
考虑求一个这个图的长度为2的链的覆盖。
假设我们求出了,就直接把长度为2的链的两端连边,新图虽然不连通,但是满足每个点的度数都是偶数。比如u->v v->w 那么可以连 u->w这条边。
作出了这个东西,直接在这上面快乐地跑前面没有限制的算法就可以了。
具体怎么做这个东西呢,考虑这个图的dfs树。每次只考虑一个点子树上的点向这个点的边,暴力匹配连边就好了。如果这个点子树上向这个边个数为奇数再把这个点到父亲的边算上,然后标记一下就好了。
代码鸽掉了,有时间写,睡觉了💨
ZROI D2
小K与赞助
考虑每个点向自己的父亲连一条容量为这个点限制,权值为0的边(在两棵树里面)
然后从源点到一个虚拟节点连边,容量为1,权值为这个点的点权(显然,一个点只能被选择一次)
然后两个树的根节点向汇点连边
跑最大费用流即可
(码的时候addedge写错调了好久。。)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cmath>
#include<queue>
#define inf 0x3f3f3f3f
using namespace std;
#define MAXN 10100
int n , m , s , t , q1 , q2;
struct edge{
int to , w , dis , nxt;
}e[MAXN<<3]; int cnt = -1;
int maxf = 0 , minc = 0 , fl[MAXN] , dis[MAXN] , pre[MAXN] ;
int head[MAXN] , lst[MAXN];
void ade( int u , int v , int w , int d ) {
e[++cnt].nxt = head[u] , e[cnt].w = w;
e[cnt].to = v , e[cnt].dis = d;
head[u] = cnt;
e[++cnt].nxt = head[v] , e[cnt].w = 0;
e[cnt].to = u , e[cnt].dis = -d;
head[v] = cnt;
}
void sfpa( );
int hd1[MAXN<<1] , nex1[MAXN<<1] , to1[MAXN<<1] , rt1 , cnt1 = 0;
int hd2[MAXN<<1] , nex2[MAXN<<1] , to2[MAXN<<1] , rt2 , cnt2 = 0;
int lim1[MAXN] , lim2[MAXN];
void ade1( int u , int v ) {
to1[++cnt1] = v , nex1[cnt1] = hd1[u] , hd1[u] = cnt1;
}
void ade2( int u , int v ) {
to2[++cnt2] = v , nex2[cnt2] = hd2[u] , hd2[u] = cnt2;
}
int fa1[MAXN] , fa2[MAXN];
void dfs1( int u , int fa ) {
for( int i = hd1[u] ; i ; i = nex1[i] ) {
int v = to1[i];
if( v == fa ) continue;
fa1[v] = u;
dfs1( v , u );
}
}
void dfs2( int u , int fa ) {
for( int i = hd2[u] ; i ; i = nex2[i] ) {
int v = to2[i];
if( v == fa ) continue;
fa2[v] = u;
dfs2( v , u );
}
}
int c[MAXN];
int main() {
memset( head , -1 , sizeof head );
cin >> n; s = 3 * n + 1 , t = 3 * n + 2;
for( int i = 1 ; i <= n ; ++ i ) scanf("%d",&c[i]);
cin >> rt1;
for( int i = 1 , u , v ; i < n ; ++ i ) {
scanf("%d%d",&u,&v);
ade1( u , v ) , ade1( v , u );
}
cin >> rt2;
for( int i = 1 , u , v ; i < n ; ++ i ) {
scanf("%d%d",&u,&v);
ade2( u , v ) , ade2( v , u );
}
memset( lim1 , 0x7f , sizeof lim1 ) , memset( lim2 , 0x7f , sizeof lim2 );
cin >> q1;
for( int i = 1 , u , x ; i <= q1 ; ++ i ) {
scanf("%d%d",&u,&x);
lim1[u] = x;
}
cin >> q2;
for( int i = 1 , u , x ; i <= q2 ; ++ i ) {
scanf("%d%d",&u,&x);
lim2[u] = x;
}
dfs1( rt1 , rt1 ) , dfs2( rt2 , rt2 );
fa1[rt1] = t , fa2[rt2] = t - n;
for( int i = 1 ; i <= n ; ++ i ) {
ade( i , fa1[i] , lim1[i] , 0 );
ade( i + n , fa2[i] + n , lim2[i] , 0 );
ade( i + 2 * n , i , 1 , 0 ) , ade( i + 2 * n , i + n , 1 , 0 );
ade( s , i + 2 * n , 1 , -c[i] );
}
sfpa( );
cout << - minc << endl;
}
bool spfa( int s , int t );
void sfpa( ) {
while( spfa( s , t ) ) {
int u = t;
maxf += fl[t] , minc += fl[t] * dis[t];
while( u != s ) {
e[lst[u]].w -= fl[t] , e[lst[u] ^ 1].w += fl[t];
u = pre[u];
}
}
}
bool vis[MAXN];
queue<int> Q;
bool spfa( int s , int t ) {
memset( dis , 0x7f , sizeof dis );
memset( fl , 0x7f , sizeof fl );
memset( vis , 0 , sizeof vis );
memset( pre , -1 , sizeof pre );
Q.push( s ) , vis[s] = 1 , dis[s] = 0;
while( !Q.empty() ) {
int u = Q.front(); Q.pop();
vis[u] = 0;
for( int i = head[u] ; ~i ; i = e[i].nxt ) {
if( e[i].w > 0 && dis[e[i].to] > dis[u] + e[i].dis ) {
dis[e[i].to] = dis[u] + e[i].dis;
pre[e[i].to] = u;
lst[e[i].to] = i;
fl[e[i].to] = min( fl[u] , e[i].w );
if( !vis[e[i].to] )
vis[e[i].to] = true , Q.push( e[i].to );
}
}
}
return pre[t] != -1;
}
小K与重建计划
前两个subtask:
暴力 乱搞
第三个:
当它是树,有答案当且仅当点权和大于等于边权和
证明:首先,充分性很显然,点权和小于边权和最后预算肯定超支啊。。
必要性:每次必定可以选择一个边两端大于边权的边,把它缩成一个点,然后归纳证明即可
然后,如果这个树有解,那么必定存在一条边使得它的权值大于它连接的点权和。假如没有,那么可以推出最后的点权和小于了边权和,与有解矛盾。
那么可以直接暴力缩,这个点就做完了。
第四个
直接在图上跑最小生成树,然后用第三个的方法套一下。
最小生成树上的边权和最小,显然是最优秀的。
正解
考虑一个以u为根的树形结构
因为整个树肯定是点权和大于边权和的,那么u的子树中必定有一个与u连着点权和大于边权和(证明类似)。因为根节点的点权相当于是个常数,可以直接考虑用子树点权和-边权和 再减去到父亲边的权从大到小排序,然后一个一个缩就可以了。
因为整个树点权和大于边权和,那么必定存在一个u的儿子v,使得v这个子树和u连着组成的这个树点权和大于边权和(证明类似于必定存在一条边的权值大于点权和)
考虑设v的子树上的点权和-边权和为A , v到u的边权为 B , u的权值为 C
那么 必定存在 v 使得 A - B + C > 0
那么我们干脆用 A - B + C 为关键字,从大到小处理每个儿子,又因为 C 永远是个常数,尽管 C 在变化也不需要在比较大小的时候用到,直接按照 A - B 排序处理即可。
每次进入这个点,如果它到它的父亲这条边拿了是增加权值就拿上,否则处理完了子树再拿。而且这么做因为sort常数有点大,跑得很慢,分两拨处理要快一些(是线性的)但是kruskal本身就带log了,懒得优化了
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<unordered_map>
#include<vector>
using namespace std;
#define MAXN 200006
#define int long long
int n , m;
long long A[MAXN];
unordered_map<long long , int> bac;
struct edge{
int u , v , w , id;
} E[MAXN] ;
bool cmp( edge a , edge b ) {
return a.w < b.w;
}
vector<int> G[MAXN] , W[MAXN];
int fa[MAXN];
int find( int x ) {
return x == fa[x] ? x : fa[x] = find( fa[x] );
}
long long Sw[MAXN] , Sl[MAXN]; int siz[MAXN] , wtf[MAXN]; // 点权和 和 链权和
void dfs( int u , int fa ) {
Sw[u] = A[u] , siz[u] = 1;
for( int i = 0 ; i < G[u].size() ; ++ i ) {
int v = G[u][i] , w = W[u][i];
if( v == fa ) continue;
Sl[u] += w , wtf[v] = w;
dfs( v , u );
siz[u] += siz[v] , Sl[u] += Sl[v] , Sw[u] += Sw[v];
}
}
int w[MAXN];
bool cmpp( int a , int b ) {
return w[a] > w[b];
}
bool cmm( int a , int b ) {
return a > b;
}
void work( int u , int fa ) {
for( int i = 0 ; i < G[u].size() ; ++ i ) {
int v = G[u][i] , ww = W[u][i];
if( v == fa ) { w[fa] = 0 , W[u][i] = 0; continue; }
w[v] = Sw[v] - Sl[v] - ww , W[u][i] = Sw[v] - Sl[v] - ww;
}
sort( G[u].begin() , G[u].end() , cmpp ) , sort( W[u].begin() , W[u].end() , cmm );
int flg = 0;
if( - wtf[u] + A[fa] >= 0 && u != 1 ) printf("%lld ",bac[ 1ll * min( u , fa ) * MAXN + max( u , fa ) ]) , flg = 1 , A[u] += A[fa] - wtf[u];
for( int i = 0 ; i < G[u].size() ; ++ i ) {
int v = G[u][i];
if( v == fa ) continue;
work( v , u ) , A[u] = A[u] + W[u][i];
}
if( !flg && u != 1 ) printf("%lld ",bac[ 1ll * min( u , fa ) * MAXN + max( u , fa ) ]) , A[u] += A[fa] - wtf[u];
}
signed main( ) {
cin >> n >> m;
long long Re = 0;
for( int i = 1 ; i <= n ; ++ i ) scanf("%lld",&A[i]) , Re += A[i];
for( int i = 1 ; i <= m ; ++ i ) {
scanf("%lld%lld%lld",&E[i].u,&E[i].v,&E[i].w) , E[i].id = i;
if( E[i].u > E[i].v ) swap( E[i].u , E[i].v );
}
sort( E + 1 , E + 1 + m , cmp );
for( int i = 1 ; i <= n ; ++ i ) fa[i] = i;
long long re = 0;
for( int i = 1 ; i <= m ; ++ i ) {
int u = E[i].u , v = E[i].v;
if( find( u ) != find( v ) ) {
fa[find( u )] = find( v );
bac[ 1ll * u * MAXN + v ] = E[i].id;
G[u].push_back( v ) , G[v].push_back( u );
W[u].push_back( E[i].w ) , W[v].push_back( E[i].w );
re += E[i].w;
}
}
if( Re < re ) return puts("0") , 0;
puts("1");
dfs( 1 , 1 );
work( 1 , 1 );
}
小K与作品
先放个别人的题解(鸽了)
考虑g(x)表示从x向前最短可以找到一个完全平方数
那么可以证明g(x) 与 f(x) 互为反函数(为什么?)
假设f(x) = y , 那么考虑从x到f(x)得到的那个完全平方数是a
考虑g(x) 得到的完全平方数是b
那么a b每个质因子质数异或起来也是0,
(剩下的证明先鸽了)
然后把l+1 , r-1 放进线性基,然后查一下 l ^ r 在里面有没有出现
然后得到了一个\(n \frac{n^2}{w}\)做法
ZROI D3
游戏
考虑删除一个木板相当于交换两端的答案
对木板排序,从下往上做,没了
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
#define MAXN 200006
int w , h , n;
int getpos( int ww , int hh ) { // 是哪一个求会落到 ww , hh
hh %= 2 * w;
int flg = 0;
if( hh >= w ) flg = 1;
hh %= w ;
++ hh;
int t = hh - ww + 1 , res = 0;
if( t <= 0 ) res = 1 , t = -t , t += 2;
if( t & 1 ) {
if( t == 1 ) res += 1;
else res += t - 1;
} else {
res = 0;
t = hh - ( w - ww );
if( t <= 0 ) res = -1 , t = -t , t += 2;
if( t & 1 ) {
if( t == 1 ) res += w;
else res += w + 2 - t;
}
}
if( flg ) res = w + 1 - res;
return res;
}
int ans[MAXN] , pre[MAXN];
struct opt{
int x , y;
} op[MAXN] ;
bool cmp( opt a , opt b ) {
return a.y > b.y;
}
int main() {
cin >> h >> w >> n;
for( int i = 1 ; i <= w ; ++ i ) pre[i] = getpos( i , h ) , ans[pre[i]] = i;
for( int i = 1 ; i <= n ; ++ i )
scanf("%d%d",&op[i].y,&op[i].x);
sort( op + 1 , op + 1 + n , cmp );
for( int i = 1 ; i <= n ; ++ i ) {
int l = op[i].x , r = op[i].x + 1 , ht = op[i].y;
swap( ans[getpos( l , ht )] , ans[getpos( r , ht )] );
}
for( int i = 1 ; i <= w ; ++ i ) printf("%d\n",ans[i]);
}
画画
两个人在从左上往右下移动
(x1 , y1) ( x2 , y2 )
x1 < x2 时 则第一个人走的路径是确定好的
y1 < y2 时 第一个人走的路径确定好的
(鸽几天)
交易
10pts
直接模拟
40pts q = 1
一条信息什么时候最晚传到终点
65pts 链
考虑左边的信息到右边的贡献 ( x -> x + 1 )
对于一条边,假设在l1,r1 和 l2,r2 是断开的,那么经过这条边时就是吧中间的一段给加到后面的区间 线段树
std1
类比链,点分治,求经过重心的点对的信息 直接合并
线段树合并
再从重心向下推一遍 类似点分治的去重
Onlog^2n
std2
假设一个点的信息数量是val 那么断开时记录一下 tmp = val
则重新联通时 就变成了X 和 Y当前信息的个数和再减去原来的
ZROI D4
猫
有决策单调性!可以直接分治 mplog
随便搞搞斜优就好了、、
$ dp[i][j] = min( dp[k][j-1] - kd_i + S_{k} ) - S_i + id_i + d_i$
$ dp[t][j-1] - td_i + S_{t} < dp[p][j-1] - pd_i + S_{p} $
$ \frac{dp[t][j-1] + S_t - dp[p][j-1] - S_p}{t-p} < d_i (t > p) $
只是很难写QAQ
然后全开longlong 100 -> 90 (常数)O mp
赛后卡了卡常终于跑过去了。。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#define RE register
using namespace std;
#define MAXN 100006
typedef long long ll;
int n , m , p;
int d[MAXN] , poi[MAXN];
ll dp[50006][1006];
ll spoi[MAXN];
int que[MAXN] , head , tail;
int j;
int read( ) {
int res = 0; char ch = ' ';
while( ch > '9' || ch < '0' ) ch = getchar();
while( ch >= '0' && ch <= '9' ) { res *= 10 , res += ch - '0' , ch = getchar(); }
return res;
}
signed main() {
cin >> n >> m >> p;
for( RE int i = 2 ; i <= n ; ++ i ) d[i] = read() , d[i] += d[i - 1];
for( RE int i = 1 , h , t ; i <= m ; ++ i ) {
h = read( ) , t = read();
poi[i] = t - d[h];
}
sort( poi + 1 , poi + 1 + m );
for( RE int i = 1 ; i <= m ; ++ i ) spoi[i] = spoi[ i - 1 ] + poi[i];
j = 1;
for( RE int i = 1 ; i <= m ; ++ i )
dp[i][1] = 1LL * i * poi[i] - spoi[i];
for( j = 2 ; j <= p ; ++ j ) {
head = 0 , tail = -1;
for( int i = 1 ; i <= m ; ++ i ) {
while( head < tail && poi[i] * ( que[head] - que[head + 1] ) < ( spoi[ que[head] ] + dp[que[head]][ j - 1 ] - spoi[que[head + 1]] - dp[que[head + 1]][j - 1] ) ) ++ head;
dp[i][j] = dp[que[head]][j - 1] + 1LL * poi[i] * ( i - que[head] ) - ( spoi[i] - spoi[que[head]] );
while( head < tail && ( spoi[ que[tail] ] + dp[que[tail]][ j - 1 ] - spoi[que[tail - 1]] - dp[que[tail - 1]][j - 1] ) * ( que[tail] - i ) < ( spoi[ que[tail] ] + dp[que[tail]][ j - 1 ] - spoi[i] - dp[i][j - 1] ) * ( que[tail] - que[tail - 1] ) ) -- tail;
que[++tail] = i;
}
}
printf("%lld",dp[m][p]);
}
设f(k) 表示恰好切k段
打表 发现 f是凸的
据说有mlogm的做法(鸽了)(但真的好强)
打地鼠
奇怪的和
ZROI D5
小D与子序列
考虑dp,dp表示前i小得到j的最少个数,顺便维护一个len表示以这个作为最大值往前多少个取到最小值
哦不用记录len了,,其实直接记录最小值转移就可以了!
考试的时候脑子混了。。。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
#define MAXN 5006
int n , m;
int dp[MAXN][MAXN] , pre[MAXN][MAXN] , len[MAXN][MAXN];
int A[MAXN];
int main() {
cin >> n >> m;
for( int i = 1 ; i <= n ; ++ i ) scanf("%d",&A[i]);
sort( A + 1 , A + 1 + n );
memset( dp , 0x3f , sizeof dp );
int minm = 0x3f3f3f3f;
for( int i = 1 ; i <= n ; ++ i ) {
for( int j = 1 ; j <= m ; ++ j ) {
if( j < A[i] ) if( dp[i - 1][j] != 0x3f3f3f3f )
{ dp[i][j] = dp[i - 1][j] , pre[i][j] = j; continue; } else;
else if( A[i] == j ) { dp[i][A[i]] = 1; }
else {
if( dp[i - 1][j - A[i]] == 0x3f3f3f3f && dp[i - 1][j] == 0x3f3f3f3f ) continue;
if( dp[i - 1][ j - A[i] ] + 1 < dp[i - 1][j] )
dp[i][j] = dp[i - 1][j - A[i]] + 1 , pre[i][j] = j - A[i];
else
dp[i][j] = dp[i - 1][j] , pre[i][j] = j;
}
}
minm = min( minm , dp[i][m] );
}
if( minm == 0x3f3f3f3f ) return puts("-1") , 0;
int ans = 0x3f3f3f3f;
for( int i = 1 ; i <= n ; ++ i )
if( dp[i][m] == minm && pre[i][m] != m ) {
int cur = m , j = i;
while( pre[j][cur] ) cur = pre[j][cur] , -- j;
ans = min( ans , A[i] - A[j] );
}
cout << ans << endl;
}
小D与字符串
首先,这个串肯定是长成这样的:
一段一段循环出现的和最后一小段循环节的前驱
那么,假设前面循环出现的段每段有\(a_i\)个ith字符,最后小段有\(b_i\)个ith字符,那么可以发现每种字符贡献是独立的(把块内randomshuffle一下结果没变嘛)那么显然有\(b_{i} \leq a_{i}\)。
并且,由于总共ith字符个数只有\(c_i\)个所以还有:
\(a_{i} \cdot k+b_{i} \leq c_{i}\)
也就是说,要在满足这两个条件的前提下,我们需要最大化
\(a_i(k - 1) + b_i\)
为啥要-1呢,因为第一块不在lcp里面。。(不然就完全重复的后缀了)
考虑什么时候答案最优,当 \(a_i\) 减少1 , \(b_i\) 增加k的时候,带入式子发现答案正好增加了1,也就是说对于每一个 $ k $ 我们希望 \(a_i\) 尽可能小,同时 \(b_i\) 尽可能大,但是也得满足前面所说的限制。
枚举k,分两种情况讨论:
-
\(b_i = a_i\)
如果可以做到 \(b_i = a_i\) 显然会让a,b相等。但是这个情况必须满足\(k+1|c_i\) ,并且\(a_i = c_i / (k + 1)\)
-
\(b_i < a_i\)
首先有 \(b_i + k > a_i - 1\),否则完全可以再进行一次减少a增加b的操作。
并且,由于\(b_i\)不能增加(除非增加k都会让答案变得不优秀)
所以,一定有$ b_i = a_i - k $
带入不等式可以推出
\(a_i \leq \lceil \frac{c_i}{k+1}\rceil\)
由于 \(b_i = a_i - k\) , 要最大化\(b_i\)就要最大化\(a_i\),又因为k+1不整除 \(c_i\) 有$a_i = \lceil \frac{c_i}{k+1}\rceil = \lfloor \frac{c_i}{k+1}\rfloor + 1 $
由于对于每个\(c_i\) \(c_i/(k+1)\) 有sqrt种取值,总复杂度\(n^2\sqrt c\)
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
typedef long long ll;
#define MAXN 27
#define int long long
int n;
ll c[MAXN];
ll mx = 0;
int work( int a , int x , int c ) {
return c - a * ( x - 1 ) >= 0 ? ( x - 2 ) * a + min( a , c - ( x - 1 ) * a ) : 0;
}
signed main() {
cin >> n;
for( int i = 1 ; i <= n ; ++i ) scanf("%lld",&c[i]) , mx = max( mx , c[i] );
ll res = 0;
for( ll k = 2 , r , s ; k <= mx + 1 ; k = r + 1 ) { // 这里其实是 k + 1
r = 0x3f3f3f3f3f3f3f3f , s = 0;
for( int i = 1 ; i <= n ; ++ i ) {
int a = c[i] / ( k );
s += max( work( a , k , c[i] ) , work( a + 1 , k , c[i] ) );
if( c[i] / k ) r = min( r , c[i] / ( c[i] / k ) );
}
res = max( res , s );
}
cout << res << endl;
}
小D与计算
提答
Task1
取反,左移63位再右移63位没了
Task2
a + b = ( a ^ b ) + ( a & b ) << 1
相当于拆成进位和不进位
每一轮是三步,最后一共190多步
Task3
直接调用前面的加法函数,右移后暴力判最低位,注意每次进位数压一下
Task4
相当于 d = x ^ y 后看最高位,用 | d >>1 ,d >>2 ,d >>4 …来把最高位后面全部变1,然后 d ^ ( d >>1 ) 就得到了只有最高位的1.
Task5 & Task6
它们鸽了
ZROI D6
今天有点自闭了啊~
蔡老板与公司
首先喷一下,这题被某带佬爆出原题了。。
由于是括号匹配,十有八九是和栈有关的。
考虑维护一个栈,每加入一个字符把它和栈顶尝试匹配,然后把整个栈的hash值算一算。如果已经出现过了,说明我们得到了合法的括号序列,然后累加进hash表。
为啥呢? 如果出现了一个合法的括号序列,这个括号序列在栈里面显然是会被消没的。消没后的这个东西如果以前也出现过,说明中间夹的就是一段合法的括号序列。当然,有可能夹着几段,所以是累加。
感性理解一下吧QWQ
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<map>
using namespace std;
typedef pair<long long,long long> py;
#define mp make_pair
#define MAXN 1000006
const long long bs1=233,mod1=1926008173;
const long long bs2=2333,mod2=1926008179;
map<py,int> M;
long long hs1[MAXN],hs2[MAXN];
py get(int i,int c){
if( i ) return mp( hs1[i] = ( hs1[i-1] * bs1 + c ) % mod1 , hs2[ i ] = ( hs2[ i-1 ] * bs2 + c ) % mod2 );
return mp(0ll,0ll);
}
char str[MAXN];
int st[MAXN],tp,n;
int main ()
{
scanf("%s",str+1);
n=strlen(str+1);
long long ans=0;
M[make_pair(0ll,0ll)]++;
for( int i = 1 ; i <= n ; i++ ) {
int c = str[i] - 'a' + 1;
if( tp && st[tp] == c ) -- tp;
else st[ ++tp ] = c;
py tmp = get( tp , st[tp] );
ans += M[tmp]++;
}
printf("%lld",ans);
return 0;
}
蔡老板与豪宅
由于不会FWT 不会 FMT 先鸽着,学了再来做
蔡老板与宝藏
一个可做但是很难写的题。。随缘看
ZROI D7
今天真自闭了。。推T1两小时推不出没想到真实个O(松)。。dls的题确实毒瘤。。
松
首先考虑类似fft一样,对读入进去的数据进行一次Rader排序。
那么每一次操作就等价于对于前一半和后一半进行一次位运算。
然后压位,每32位压进一个unsigned int里面,进行操作时直接把前面一半和后面一半进行暴力一次操作,并且放进一个新的数组递归进行。
对于前2^16进行预处理,真实复杂度就会减少五层大概是\(3^{n-5}\), 4e7可以跑QWQ。
// 你好松啊~
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 2100000
typedef unsigned int ui;
int n , A[MAXN]; ui a[MAXN];
int rad[MAXN] , pre[MAXN];
int dfs1( int dep , ui x ) {
if( ! dep ) return x & 1;
return dfs1( dep - 1 , x & ( x >> ( 1 << dep - 1 ) ) ) + dfs1( dep - 1 , x | ( x >> ( 1 << dep - 1 ) ) ) + dfs1( dep - 1 , x ^ ( x >> ( 1 << dep - 1 ) ) );
}
int dfs2( int dep , ui* c ) {
if( dep == 5 ) return pre[ (*c & ( *c >> 16 )) & 65535 ] + pre[ (*c | ( *c >> 16 )) & 65535 ] + pre[ (*c ^ ( *c >> 16 )) & 65535 ];
ui *t = c + ( 1 << dep - 5 );
int res = 0;
for( int i = 0 ; i < ( 1 << dep - 6 ) ; ++ i ) t[i] = c[i] & c[i + (1 << dep - 6)] ;
res += dfs2( dep - 1 , t );
for( int i = 0 ; i < ( 1 << dep - 6 ) ; ++ i ) t[i] = c[i] | c[i + (1 << dep - 6)] ;
res += dfs2( dep - 1 , t );
for( int i = 0 ; i < ( 1 << dep - 6 ) ; ++ i ) t[i] = c[i] ^ c[i + (1 << dep - 6)] ;
res += dfs2( dep - 1 , t );
return res;
}
int main() {
cin >> n;
for( int i = 0 ; i < ( 1 << n ) ; ++ i ) scanf("%1d",&A[i]);
rad[0] = 0;
for( int i = 1 ; i < ( 1 << n ) ; ++ i )
rad[i] = ( rad[i >> 1] >> 1 ) | ( ( i & 1 ) << n - 1 );
for( int i = 0 ; i < ( 1 << n ) ; ++ i ) if( i < rad[i] )
swap( A[i] , A[rad[i]] );
for( int i = 0 ; i < ( 1 << n ) ; ++ i )
a[i >> 5] |= A[i] << ( i & 31 );
if( n <= 5 ) return printf( "%d\n",dfs1( n , * a ) ) , 0;
for( int i = 0 ; i < ( 1 << 16 ) ; ++ i ) pre[i] = dfs1( 4 , i );
printf( "%d\n",dfs2( n , a ) );
}
集合
如果考场上可以想到枚举个数就好了呢~
首先,考虑当最小差和最大差都枚举了的时候怎么做。
对选出的序列排序后差分一下,假设差分后是\(x_{1...n}\),那么显然
\(\sum x_i = mx , MIN x_i = mn\)
然后发现这就是个方程啊!插板法一下就没了呢~
但是这样做瓶颈在于枚举个数。这样是\(O(n^3)\)的呢!
那么如果最开始就枚举最小差和个数呢?
由于枚举了最小差,个数不超过n/d
这是Onlog的!
假设最小差是\(i\) , 个数是\(j\)
此时我们要算的是 \(i \times \sum \Delta mx\)
为了算最大差的和,只需要算所有的最大值和所有的最小值然后剪一下
枚举最小值是c,贡献值是
\(\displaystyle\sum_{c=1}^{n-(i-1)(j-1)} C_{n-(i-1)(j-1)-c}^{j-1} \times c\)
然而可以推出最大值贡献是(n+1)x - 最小值的和(我也不知道为啥)
剩下的再鸽会~
ZROI D8
自闭了。
随缘写。
ZROI D9
快乐链覆盖
zblzbl
一个比较裸的树形dp,但是有个问题是咋输出方案。。具体看题解吧。。码事不可能码的这辈子都不可能码的(🐦
考试直接30分暴力拆边直径走人
快乐线段树
结论题,具体看题解(
快乐KMP
这题抽空一定写写(🐦
后缀树好题啊(
ZROI D10
这是最惨的一天。。
原本想连续十天rating单调递增。。结果最简单的一天翻车了。。。。。。
T1爆0太真实了(
普通LCP
后缀间lcp。。裸height数组。。求出来后单调栈一下就没了
然而标算是后缀树(感觉没啥问题啊为啥我爆0了。。
1e6的话。。rmq能跑的吧。。
(🐦)
优秀的Tree
原题。。数组没开2倍。。。。。。。。。
**** 中日双语
#include "iostream"
#include "algorithm"
#include "cstring"
#include "cstdio"
using namespace std;
#define MAXN 400006
#define P 1000000007
typedef long long ll;
ll head[MAXN] , to[MAXN] , nex[MAXN] , cnt;
void ade( int u , int v ) {
to[++cnt] = v , nex[cnt] = head[u] , head[u] = cnt;
}
ll n,siz[MAXN],sum[MAXN];
ll c[MAXN];
ll res;
//siz 为 子树 大小 sum 为当前处理过 的 所有 颜色为 c 的 点的 子树 上 的点的个数
//res 为一个块里面的 选两个点 的个数
void dfs(ll u,ll fa){
siz[u] = 1;
ll last = sum[c[u]];
for( int i = head[u] ; i ; i = nex[i] ) {
ll v = to[i];
if( v == fa ) continue;
ll cur = sum[c[u]];
dfs(v,u);
siz[u] += siz[v];
ll sz = siz[v] - sum[c[u]] + cur; sz %= P;
res -= sz*(sz-1)/2 , res += P , res %= P;
}
sum[c[u]] = last + siz[u] , sum[c[u]] %= P;
}
ll J[MAXN];
int main() {
res = 0;
cin >> n;
J[0] = 1;for( int i = 1 ; i <= n ; ++ i ) J[i] = J[ i - 1 ] * i , J[i] %= P;
for (ll i = 1; i <= n; ++i) scanf("%lld", &c[i]);
for (ll i = 0; i < n - 1; ++i) {
static ll u, v;
scanf("%lld%lld", &u, &v);
ade( u , v ) , ade( v , u );
}
dfs(1, 1);
for (ll i = 1; i <= n; ++i)
if (sum[i]) res += n * (n - 1) / 2 , res -= (n - sum[i]) * (n - sum[i] - 1) / 2 , res %= P;
printf("%lld\n", res % P * 2 % P * J[n - 1] % P );
}
猛男splay
鸽~