文章目录
CF1251C Minimize The Integer
给你一个大整数 a a a,它由 n n n位数字,也可能有前导零。
现在给你一种操作规则:如果相邻的两个数字的奇偶性不同,那么你就可以交换它们。
现在可以做任意次操作(可能一次都不做),求出通过这些操作可以获得的最小整数是多少。
答案可以包含前导零
observation
: 相邻位才能交换,且要奇偶不同,所以同奇偶间的顺序一定不会改变(不满足交换条件)
奇偶不同之间的数就可以相互交换
这其实是一个归并排序的过程,两个队列模拟即可
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
#define maxn 300005
queue < int > odd, even, ans;
char s[maxn];
int n;
int main() {
scanf( "%s", s + 1 );
n = strlen( s + 1 );
for( int i = 1;i <= n;i ++ ) {
int x = s[i] - '0';
if( x & 1 ) odd.push( x );
else even.push( x );
}
while( ! odd.empty() and ! even.empty() ) {
if( odd.front() < even.front() ) ans.push( odd.front() ), odd.pop();
else ans.push( even.front() ), even.pop();
}
while( ! odd.empty() ) ans.push( odd.front() ), odd.pop();
while( ! even.empty() ) ans.push( even.front() ), even.pop();
while( ! ans.empty() ) printf( "%d", ans.front() ), ans.pop();
return 0;
}
acwing164:可达性统计
给定一张 n n n个点 m m m条边的有向无环图,你需要分别统计从每个点出发能够到达的点的数量
observation
: 保证是有向无环图,明显可以用拓扑解决有向图问题
但是很有可能多个点到的点有重复,那么单纯的用 d p i dp_i dpi( i i i所能到达的点)进行累加便不可取,会算重
数据范围 n ≤ 30000 n\le 30000 n≤30000,发现 n 2 / 32 n^2/32 n2/32其实是可以接受的
那么就可以用bitset
优化,直接记录能到达的点集,那么转移就是取并
最后统计1
的个数就行,这就直接知道到哪些点,便不会算重了
#include <iostream>
#include <bitset>
#include <vector>
#include <cstdio>
#include <queue>
#include <map>
using namespace std;
#define maxn 30005
map < pair < int, int >, int > mp;
bitset < maxn > dp[maxn];
vector < int > G[maxn];
queue < int > q;
int n, m;
int ans[maxn], d[maxn];
int main() {
scanf( "%d %d", &n, &m );
for( int i = 1, u, v;i <= m;i ++ ) {
scanf( "%d %d", &u, &v );
if( mp[make_pair( u, v )] ) continue;
else mp[make_pair( u, v )] = 1;
G[v].push_back( u );
d[u] ++;
}
for( int i = 1;i <= n;i ++ )
if( ! d[i] ) q.push( i );
while( ! q.empty() ) {
int u = q.front(); q.pop();
dp[u][u] = 1;
for( auto v : G[u] ) {
d[v] --;
dp[v] |= dp[u];
if( ! d[v] ) q.push( v );
}
}
for( int i = 1;i <= n;i ++ )
printf( "%d\n", dp[i].count() );
return 0;
}
Facebook Hacker Cup 2016 Round 1 Boomerang Tournament
这个周末,期待已久的BIT(回旋镖邀请赛)将举行! N N N个回旋镖选手将随机配对,进行单淘汰赛。
可以按以下方式解释这种锦标赛规则:
- N N N个选手以某种顺序排列在队列中(有序列表)
- 如果队列当前仅包含 1 1 1个选手,则比赛结束,该选手作为冠军
- 否则,取出排在队列最前面的 2 2 2名选手,让他们比赛
- 比赛的获胜者重新插入到队列的最后面
- 从步骤 2 2 2重复
当第 i i i个和第 j j j个选手比赛时
- 如果 W i , j = 1 W_{i,j} = 1 Wi,j=1时则第 i i i个选手将获胜
- 否则,第j个选手将获胜。
- 请注意,对于所有 ( 1 ≤ i , j ≤ N ) , W i , j = 0 / 1 (1≤i,j≤N),W_{i,j}= 0/1 (1≤i,j≤N),Wi,j=0/1,并且 W i , i = 0 W_{i,i}= 0 Wi,i=0(无论如何也不会与自己对战),并且 W i , j ≠ W j , i W~i,j~≠W~j,i~ W i,j =W j,i (如果 i ≠ j i ≠j i=j)
注意: A击败B,B击败C,C击败A是有可能的
比赛结束后,每个选手都会获得排名(即使他们在比赛中没能幸存下来)
某个选手T的排名是一个整数,这个整数比排在他前面的且离他最近的某个选手S排名大1,
S赢得比赛的场数严格大于T赢得比赛的场数.
因为初始对局的顺序未知,所以对于每个选手,想知道他们最终可能会获得的最好(最小)和最差(最大)排名.
T , n ≤ 16 T,n\le 16 T,n≤16,保证 n n n为2的幂
最坏名次很好求,只要有人能打败他,他就可以在第一轮被淘汰
最好名次用状压
设
d
p
s
,
i
:
dp_{s,i}:
dps,i: 在对抗人数集合为
s
s
s状态时,
i
i
i成为最后胜者的可行性0/1
不可以/可以
- 显然对抗人数一定是 2 2 2的幂,这可以剪枝掉很多不必要的状态
写法尝试了很多种
-
写法一
枚举状态 s s s,枚举 i i i,再枚举状态 t t t,并保证 s , t s,t s,t的 1 1 1个数一样(是同一层的角逐),还要枚举 j j j表示 t t t的优胜者
就算加了很多剪枝,判断优胜者是否属于集合,是否可行
但仍有很多不必要的枚举
-
优化写法一后的写法二
b i t k bit_k bitk: 1 1 1的个数为 k k k的所有集合 s s s
n u m s num_s nums: s s s状态中为 1 1 1的位置,相当于预处理出所有参赛人员,最后的优胜者一定对应位置为 1 1 1
当 n n n达到 16 16 16的时候 s , t s,t s,t的枚举还是会超时
-
优化写法二的最终写法
枚举状态 n o w now now,以及该层比赛的上一次比赛(半决赛) s s s,通过 ⨁ \bigoplus ⨁可以求得另一半决赛的状态 t t t
然后枚举两场半决赛的各自的优胜者
根据优胜者之间 g g g的关系,决出最后的胜者成为 n o w now now状态的优胜者
再配合上一下集合完全包含的剪枝即可通过了
n = 16 n=16 n=16状态数为 65536 65536 65536剪枝后又无法承受平方的枚举
但是如果枚举一次后,里面枚举的只有一半,状态数就会锐减为 2 8 2^8 28
最后对于每一个 i i i找出自己是优胜者的所有比赛中参赛人员最多的( 1 1 1最多的状态)
就是最好成绩
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
vector < int > bit[20], num[1 << 20];
int dp[1 << 16][20], g[20][20];
int T, n;
bool ok[20];
int lowbit( int x ) { return x & -x; }
int main() {
ok[1] = ok[2] = ok[4] = ok[8] = ok[16] = 1;
scanf( "%d", &T );
for( int Case = 1;Case <= T;Case ++ ) {
scanf( "%d", &n );
for( int i = 0;i < n;i ++ )
for( int j = 0;j < n;j ++ )
scanf( "%d", &g[i][j] );
memset( dp, 0, sizeof( dp ) );
for( int i = 0;i < n;i ++ ) dp[1 << i][i] = 1;
for( int i = 1;i <= n;i ++ ) bit[i].clear();
for( int s = 1;s < ( 1 << n );s ++ )
if( ! ok[__builtin_popcount( s )] ) continue;
else {
bit[__builtin_popcount( s )].push_back( s );
if( num[s].size() ) continue;
else
for( int i = 0;i < n;i ++ )
if( s >> i & 1 ) num[s].push_back( i );
}
for( int k = 2;k <= n;k <<= 1 )
for( int now : bit[k] )
for( int s : bit[k >> 1] ) {
if( ( now & s ) != s ) continue;
int t = now ^ s;
if( s > t ) continue;
for( int i : num[s] ) {
if( ! dp[s][i] ) continue;
for( int j : num[t] ) {
if( ! dp[t][j] ) continue;
if( g[i][j] ) dp[now][i] = 1;
else dp[now][j] = 1;
}
}
}
printf( "Case #%d:\n", Case );
for( int i = 0;i < n;i ++ ) {
bool flag = 1;
for( int j = 0;j < n;j ++ )
if( i == j ) continue;
else flag &= g[i][j];
int best, worst, cnt = 0;
if( flag ) worst = 1;
else worst = ( n >> 1 ) + 1;
for( int s = 1;s < ( 1 << n );s ++ )
if( dp[s][i] ) cnt = max( cnt, __builtin_popcount( s ) );
if( cnt == n ) best = 1;
else if( cnt == ( n >> 1 ) ) best = 2;
else if( cnt == ( n >> 2 ) ) best = 3;
else if( cnt == ( n >> 3 ) ) best = 5;
else best = 9;
printf( "%d %d\n", best, worst );
}
}
return 0;
}
[Zjoi2012]小蓝的好友(mrx)
终于到达了这次选拔赛的最后一题,想必你已经厌倦了小蓝和小白的故事,为了回馈各位比赛选手,此题的主角是贯穿这次比赛的关键人物——小蓝的好友。
在帮小蓝确定了旅游路线后,小蓝的好友也不会浪费这个难得的暑假。与小蓝不同,小蓝的好友并不想将时间花在旅游上,而是盯上了最近发行的即时战略游戏——SangoCraft。但在前往通关之路的道路上,一个小游戏挡住了小蓝的好友的步伐。
“国家的战争其本质是抢夺资源的战争”是整款游戏的核心理念,这个小游戏也不例外。简单来说,用户需要在给定的长方形土地上选出一块子矩形,而系统随机生成了N个资源点,位于用户所选的长方形土地上的资源点越多,给予用户的奖励也越多。悲剧的是,小蓝的好友虽然拥有着极其优秀的能力,但同时也有着极差的RP,小蓝的好友所选的区域总是没有一个资源点。
终于有一天,小蓝的好友决定投诉这款游戏的制造厂商,为了搜集证据,小蓝的好友想算出至少包含一个资源点的区域的数量。作为小蓝的好友,这自然是你分内之事。
Input
第一行包含两个由空格隔开的正整数R,C,N,表示游戏在一块[1,R]x[1,C]的地图上生成了N个资源点
接下来有N行,每行包含两个整数 x,y,表示这个资源点的坐标(1<=x<=R,1<=Y<=c)
Output
输出文件应仅包含一个整数,表示至少包含一个资源点的区域的数量
具体的说,设N个资源点的坐标为(i=1…n),你需要计算有多少个四元组(LB,DB,RB,UB)满足1<=LB<=RB<=R,1<=DB<=UB<=C,且存在一个i使得LB<=Xi<=RB,DB<=Yi<=UB均成立
Sample Input
5 5 4
1 2
2 3
3 5
4 1
Sample Output
139
Hint
【数据范围】
对于100%的数据,R,C<=40000,N<=100000,资源点的位置两两不同,且位置为随机生成
首先转换成,总矩阵数量减去一条鱼都不包含的矩阵数量
然后枚举每一行 i i i,当做矩阵的底
这就有点像求最大全 1 1 1矩阵了
对于每一列处理出在 i i i行以上最近的鱼的距离,当成这一列的高
那这就是刚做的笛卡尔树了
根据笛卡尔树的根,把左右儿子分开独立计算
贡献为 ( h [ x ] − h [ f a [ x ] ] ) ∗ s i z [ x ] ∗ ( s i z [ x ] + 1 ) / 2 (h[x]-h[fa[x]])*siz[x]*(siz[x]+1)/2 (h[x]−h[fa[x]])∗siz[x]∗(siz[x]+1)/2
当枚举行往下移的时候,相当于整体+1
,如果某列在当前枚举行有鱼,高度设为
0
0
0就可以了
相当于对笛卡尔树进行两种操作,全局+1
和单点修改为
0
0
0
这可以用fhq-treap
暴力不动树
但是笛卡尔树作为一种二叉树,又满足小根堆性质,发现用带旋treap
维护树不会改变本质
#include <cstdio>
#include <vector>
using namespace std;
#define maxn 100005
#define int long long
#define lson t[x].son[0]
#define rson t[x].son[1]
vector < int > g[maxn];
struct node { int l, r, h, f, tag, siz, son[2]; }t[maxn];
int dp[maxn];
int n, m, N;
int calc( int x ) { return x * ( x + 1 ) >> 1; }
void pushup( int x ) {
t[x].siz = t[lson].siz + t[rson].siz + 1;
dp[x] = dp[lson] + dp[rson] + ( t[x].h - t[t[x].f].h ) * calc( t[x].siz );
}
void rotate( int x ) {
int fa = t[x].f;
int Gfa = t[fa].f;
int k = t[fa].son[1] == x;
if( Gfa ) t[Gfa].son[t[Gfa].son[1] == fa] = x;
t[x].f = Gfa;
t[fa].son[k] = t[x].son[k ^ 1];
t[t[x].son[k ^ 1]].f = fa;
t[x].son[k ^ 1] = fa;
t[fa].f = x;
if( t[fa].son[k] ) pushup( t[fa].son[k] );
pushup( fa );
pushup( x );
}
void add( int x, int val ) {
t[x].h += val;
t[x].tag += val;
pushup( x );
}
void pushdown( int x ) {
if( t[x].f ) pushdown( t[x].f );
if( lson ) add( lson, t[x].tag );
if( rson ) add( rson, t[x].tag );
t[x].tag = 0;
}
int build( int l, int r ) {
if( l > r ) return 0;
int x = ( l + r ) >> 1;
lson = build( l, x - 1 );
rson = build( x + 1, r );
if( lson ) t[lson].f = x;
if( rson ) t[rson].f = x;
pushup( x );
return x;
}
signed main() {
scanf( "%lld %lld %lld", &n, &m, &N );
int rt = build( 1, m );
for( int i = 1, x, y;i <= N;i ++ ) {
scanf( "%lld %lld", &x, &y );
g[x].push_back( y );
}
int ans = 0;
for( int i = 1;i <= n;i ++ ) {
add( rt, 1 );
for( int x : g[i] ) {
pushdown( x );
while( t[x].f ) rotate( x );
t[x].h = 0;
if( lson ) pushup( lson );
if( rson ) pushup( rson );
pushup( x );
rt = x;
}
ans += dp[rt];
}
printf( "%lld\n", calc( n ) * calc( m ) - ans );
return 0;
}