文章目录
红色文件
-
ARC 122 F
-
LATOKEN-Round-1(Div.1+Div.2) F2,G,H
-
#726-Div.2 F
-
#727 E,F
-
#698 C,E,F
-
GR-12 G,H1,H2
明显地,CF/AT简单题也变得很难敲了o(╥﹏╥)o哭唧唧
CodeForces
LATOKEN-Round-1(Div.1+Div.2)
A. Colour the Flag
直接
b
f
s
bfs
bfs一层层确定颜色,判断是否冲突即可,注意整张图都是.
的情况
#include <queue>
#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 55
queue < pair < int, int > > q;
int T, n, m;
char g[maxn][maxn], ans[maxn][maxn];
bool inside( int x, int y ) {
if( x < 1 || y < 1 || x > n || y > m ) return 0;
else return 1;
}
bool check( int x, int y, char c ) {
if( ! inside( x, y ) ) return 0;
if( ans[x][y] == '.' ) {
if( c == 'W' ) ans[x][y] = 'R';
else ans[x][y] = 'W';
q.push( make_pair( x, y ) );
return 0;
}
if( ans[x][y] == c ) return 1;
else return 0;
}
int main() {
scanf( "%d", &T );
again :
while( T -- ) {
while( ! q.empty() ) q.pop();
scanf( "%d %d", &n, &m );
for( int i = 1;i <= n;i ++ )
scanf( "%s", g[i] + 1 );
for( int i = 1;i <= n;i ++ )
for( int j = 1;j <= m;j ++ )
if( g[i][j] != '.' ) {
q.push( make_pair( i, j ) );
ans[i][j] = g[i][j];
}
else ans[i][j] = '.';
if( q.empty() ) {
ans[1][1] = 'R';
q.push( make_pair( 1, 1 ) );
}
while( ! q.empty() ) {
int x = q.front().first, y = q.front().second; q.pop();
if( check( x - 1, y, ans[x][y] ) || check( x + 1, y, ans[x][y] ) || check( x, y - 1, ans[x][y] ) || check( x, y + 1, ans[x][y] ) ) {
printf( "NO\n" );
goto again;
}
}
printf( "YES\n" );
for( int i = 1;i <= n;i ++ ) {
for( int j = 1;j <= m;j ++ )
printf( "%c", ans[i][j] );
printf( "\n" );
}
}
return 0;
}
B. Histogram Ugliness
不难发现,对于相邻的 i , i + 1 i,i+1 i,i+1,如果高度不同,那么操作消更高一个一定会是答案变小
对于相邻的 i − 1 , i , i + 1 i-1,i,i+1 i−1,i,i+1而言,假设最高的是 i i i,那么消到 i − 1 , i + 1 i-1,i+1 i−1,i+1两者较高高度时答案最小
再往下消答案不变,所以直接扫一遍找中间 i i i是最高进行消即可
#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
#define maxn 400005
int T, n;
int a[maxn];
int Fabs( int x ) {
return x < 0 ? -x : x;
}
signed main() {
scanf( "%lld", &T );
while( T -- ) {
scanf( "%lld", &n );
a[n + 1] = 0;
for( int i = 1;i <= n;i ++ )
scanf( "%lld", &a[i] );
int ans = 0;
for( int i = 1;i <= n;i ++ ) {
int l = a[i - 1], r = a[i + 1];
if( a[i] > l && a[i] > r ) {
int maxh = max( l, r );
ans += a[i] - maxh;
a[i] = maxh;
}
}
for( int i = 1;i <= n + 1;i ++ )
ans += Fabs( a[i] - a[i - 1] );
printf( "%lld\n", ans );
}
return 0;
}
C. Little Alawn’s Puzzle
因为两个都是排列,所以当交换 ( i , j ) (i,j) (i,j)后就必须不断的交换重复数字来抵消影响
其实是找环的个数( s i s_i si通过与 t t t的映射最后回到 s i s_i si的路径条数)
答案为 2 c n t 2^{cnt} 2cnt
#include <cstdio>
#define mod 1000000007
#define int long long
#define maxn 400005
int T, n;
int s[maxn], t[maxn], g[maxn];
bool vis[maxn];
int qkpow( int x, int y ) {
int ans = 1;
while( y ) {
if( y & 1 ) ans = ans * x % mod;
x = x * x % mod;
y >>= 1;
}
return ans;
}
signed main() {
scanf( "%lld", &T );
again :
while( T -- ) {
scanf( "%lld", &n );
for( int i = 1;i <= n;i ++ )
vis[i] = 0, scanf( "%lld", &s[i] );
for( int i = 1;i <= n;i ++ )
scanf( "%lld", &t[i] );
for( int i = 1;i <= n;i ++ )
if( s[i] == t[i] ) {
printf( "0\n" );
goto again;
}
else g[s[i]] = t[i];
int cnt = 0;
for( int i = 1;i <= n;i ++ ) {
if( vis[s[i]] ) continue;
cnt ++;
int now = s[i], flag = 1;
while( flag || now != s[i] ) {
vis[now] = 1;
now = g[now];
flag = 0;
}
}
printf( "%lld\n", qkpow( 2, cnt ) );
}
return 0;
}
D. Lost Tree
显然,不管询问哪一个点,一定有点与之距离为 1 1 1(有直接树边相连)
先问一次1
,把整棵树按距离分层建起来,再按奇偶分
哪边含的点少问哪边(问一层里的所有点,就把相邻两层的信息点找完了)
这样上线就能卡满 ⌈ n 2 ⌉ \lceil\frac{n}{2}\rceil ⌈2n⌉
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define maxn 2005
vector < int > g[2];
vector < pair < int, int > > ans;
int n;
int d[maxn];
bool vis[maxn][maxn];
void ask( int x ) {
printf( "? %d\n", x );
fflush( stdout );
for( int i = 1;i <= n;i ++ )
scanf( "%d", &d[i] );
}
int main() {
scanf( "%d", &n );
ask( 1 );
for( int i = 2;i <= n;i ++ )
g[d[i] & 1].push_back( i );
if( g[0].size() > g[1].size() ) swap( g[0], g[1] );
for( int i = 1;i <= n;i ++ )
if( d[i] == 1 ) ans.push_back( make_pair( 1, i ) ), vis[1][i] = vis[i][1] = 1;
for( int i = 0;i < g[0].size();i ++ ) {
ask( g[0][i] );
for( int j = 1;j <= n;j ++ )
if( d[j] == 1 ) {
if( vis[g[0][i]][j] ) continue;
else {
vis[g[0][i]][j] = vis[j][g[0][i]] = 1;
ans.push_back( make_pair( g[0][i], j ) );
}
}
}
printf( "!\n" );
for( int i = 0;i < ans.size();i ++ )
printf( "%d %d\n", ans[i].first, ans[i].second );
return 0;
}
E. Lost Array
求整个序列的异或和
⇔
\Leftrightarrow
⇔ 每个数的操作次数为奇数1-0-1-0-1...
两种方法,bfs
最短路/dp
(本质是一样的)
设 f i : f_i: fi: 到目前操作为止一共有 i i i个数的值没有被抵消
枚举下一次有 j j j个数重复(这 j j j个数将变成没有贡献,而多了 k − j k-j k−j个数的异或和)
最后 f n f_n fn即为最小操作次数
记录下每次最优转移的前驱以及重复个数,搜索还原即可
#include <queue>
#include <cstdio>
#include <cstring>
using namespace std;
#define maxn 505
#define inf 0x3f3f3f3f
queue < int > q;
int n, k, ans, ret;
int f[maxn], pre[maxn], val[maxn];
bool vis[maxn];
void dfs( int now ) {
if( ~ pre[now] ) dfs( pre[now] );
else return;
//cnt1:appeared
//cnt2:disappeared
printf( "?" );
for( int cnt1 = val[now], cnt2 = k - val[now], i = 1;cnt1 || cnt2;i ++ ) {
if( vis[i] ) {
if( cnt1 ) vis[i] ^= 1, printf( " %d", i ), cnt1 --;
else;
} else {
if( cnt2 ) vis[i] ^= 1, printf( " %d", i ), cnt2 --;
else;
}
}
printf( "\n" );
fflush( stdout );
scanf( "%d", &ans );
ret ^= ans;
}
int main() {
memset( pre, -1, sizeof( pre ) );
scanf( "%d %d", &n, &k );
memset( f, 0x3f, sizeof( f ) );
f[0] = 0;
q.push( 0 );
while( ! q.empty() ) {
int used = q.front(); q.pop();
//used: how many numbers have contributed
for( int i = 0;i <= k;i ++ ) {//next query has tot in i numbers same to used
if( i <= used && k - i <= n - used ) {//k<=n-used+i
int nxt = used - i + k - i;
if( nxt > 0 && nxt <= n && f[used] + 1 < f[nxt] ) {
f[nxt] = f[used] + 1;
val[nxt] = i;
pre[nxt] = used;
q.push( nxt );
}
}
}
}
if( f[n] == inf ) return ! printf( "-1\n" );
else dfs( n );
printf( "! %d\n", ret );
return 0;
}
F1. Falling Sand (Easy Version)
把所有土块移出网格
把自动土块能干扰的土块连有向边,这样的暴力建图是 n 2 n^2 n2的
事实上只需要知道上下左右最先干扰的土块,然后影响传递即可,建图锐减至 4 n 4n 4n
图中可能包含强联通图(其中一个松动,最后都会出局),缩点tarjan
那么最后其实就是找入度为 0 0 0的土块松动(没有土块能触发它们)
PS:简单版保证 a i a_i ai等于该列土块数量,所以俺干脆直接不读了
#include <map>
#include <stack>
#include <cstdio>
#include <vector>
using namespace std;
#define maxn 400005
stack < int > sta;
vector < int > G[maxn], mp[maxn], id[maxn];
int n, m, cnt, Cnt, tot;
char s[maxn];
int dfn[maxn], low[maxn], last[maxn], scc[maxn], d[maxn];
void addEdge( int u, int v ) {
G[u].push_back( v );
}
void tarjan( int u ) {
dfn[u] = low[u] = ++ Cnt;
sta.push( u );
for( int i = 0;i < G[u].size();i ++ ) {
int v = G[u][i];
if( ! dfn[v] ) {
tarjan( v );
low[u] = min( low[u], low[v] );
}
else if( ! scc[v] )
low[u] = min( low[u], dfn[v] );
}
if( low[u] == dfn[u] ) {
int v; ++ tot;
do {
v = sta.top(); sta.pop();
scc[v] = tot;
} while( v != u );
}
}
int main() {
scanf( "%d %d", &n, &m );
mp[0].resize( m + 2 ), id[0].resize( m + 2 );
for( int i = 1;i <= n;i ++ ) {
mp[i].resize( m + 2 ), id[i].resize( m + 2 );
scanf( "%s", s + 1 );
for( int j = 1;j <= m;j ++ )
mp[i][j] = s[j], id[i][j] = ++ cnt;
}
for( int i = n;i;i -- ) {
for( int j = 1;j <= m;j ++ ) {
if( mp[i][j] == '.' ) continue;
if( mp[i - 1][j] == '#' ) addEdge( id[i][j], id[i - 1][j] );
if( last[j] ) addEdge( id[i][j], last[j] );
if( mp[i][j - 1] == '#' ) addEdge( id[i][j], id[i][j - 1] );
else if( last[j - 1] ) addEdge( id[i][j], last[j - 1] );
if( mp[i][j + 1] == '#' ) addEdge( id[i][j], id[i][j + 1] );
else if( last[j + 1] ) addEdge( id[i][j], last[j + 1] );
}
for( int j = 1;j <= m;j ++ )
if( mp[i][j] == '#' ) last[j] = id[i][j];
}
for( int i = 1;i <= n;i ++ )
for( int j = 1;j <= m;j ++ )
if( mp[i][j] == '#' && ! dfn[id[i][j]] ) tarjan( id[i][j] );
for( int i = 1;i <= cnt;i ++ )
for( int j = 0;j < G[i].size();j ++ )
if( scc[i] ^ scc[G[i][j]] ) ++ d[scc[G[i][j]]];
int ans = 0;
for( int i = 1;i <= tot;i ++ )
ans += ( d[i] == 0 );
printf( "%d\n", ans );
return 0;
}
#726-Div.2
A. Arithmetic Array
总和如果比
n
n
n大,意味着需要不断补
0
0
0位置,sum-n
反之,只需要加一个正整数,1
B. Bad Boy
最远的两端(1,1)
和(n,m)
C. Challenging Cliffs
排序后直接找差距最小的两个(有多对,任选一对即可)然后按高度,比第一座高的按高度递增排列,最高的后面肯定接的是最矮的(出去两个标志建筑)没选的矮的递增排列,形成一个斜Z
形
D. Deleting Divisors
结论先猜后证yyds
case1:
n = 2 k + 1 n=2k+1 n=2k+1 奇数Bob
-
n
=
2
k
n=2k
n=2k 偶数
case2:
n = 2 t n=2^t n=2t 2 2 2的幂t=2T+1->Bob
t=2T->Alice
case3:
n = 2 t × ( 2 p + 1 ) n=2^t\times (2p+1) n=2t×(2p+1) 非 2 2 2的幂Alice
case1
减去一个奇数除数(因为所有的除数都是奇数),得到一个不是 2 2 2的幂的偶数
如果 d d d是 n n n的除数,那么 n − d n−d n−d也必须能被 d d d整除,因为 d d d是奇数, n − d n−d n−d不是 2 2 2的幂
case3
减去这个奇数除数,我们将得到 n − d n−d n−d是奇数
要么对方拿到的奇数是质数——直接失败;要么就是case1
操作后还一个不是
2
2
2的幂的偶数
所以总是不败的,因此这种局面先手必胜;故此可以退出case1
奇数局面先手必败
case2
要么减半 n n n要么使 n n n成为不是 2 2 2的幂的偶数(已经证明了这是另一个玩家必胜的状态)
唯一的方法就只能是将 n n n减半,使其成为 2 2 2的另一个幂,知道有一个拿到 2 2 2,质数输掉比赛
所以局面跟 log 2 n \log_2n log2n的奇偶挂钩
E. Erase and Extend
对于简单版本,直接暴力填充完所有位置,然后分情况
如果 s 1 < s i s_1<s_i s1<si那么从这里开始以 s 1 , i − 1 s_{1,i-1} s1,i−1为周期直接重新填充
如果 s 1 = s i s_1=s_i s1=si就从 i i i开始以长度为 i − 1 i-1 i−1与 s 1 , i − 1 s_{1,i-1} s1,i−1一一对比,如果更小,也要选择重新填充
#include <cstdio>
#define maxn 10005
int n, k;
char s[maxn];
int main() {
scanf( "%d %d %s", &n, &k, s + 1 );
while( n < k ) {
for( int i = 1;i <= n;i ++ )
s[i + n] = s[i];
n <<= 1;
}
int now = 1;
for( int i = 2;i <= k;i ++ ) {
if( s[now] > s[i] ) continue;
else if( s[now] < s[i] ) {
int t = 1;
for( int j = i;j <= k;j ++ ) {
s[j] = s[t];
t ++;
if( t == i ) t = 1;
}
}
else {
bool flag = 0;
for( int j = i;j <= ( i - 1 << 1 );j ++ )
if( s[j - i + 1] < s[j] ) {
flag = 1;
break;
}
else if( s[j - i + 1] > s[j] ) break;
if( flag ) {
int t = 1;
for( int j = i;j <= k;j ++ ) {
s[j] = s[t];
t ++;
if( t == i ) t = 1;
}
}
}
}
for( int i = 1;i <= k;i ++ )
printf( "%c", s[i] );
return 0;
}
困难版本 比简单版本还好敲 不能暴力的
O
(
n
k
)
O(nk)
O(nk)走人
用另外的数组存模板串,最后的答案为模板串的周期循环
#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 500005
int n, k;
char s[maxn];
int main() {
scanf( "%d %d %s", &n, &k, s + 1 );
int len = 1;
for( int i = 2;i <= min( n, k );i ++ ) {
int pos = ( i - 1 ) % len + 1;
if( s[pos] > s[i] ) len = i;
else if( s[pos] < s[i] ) break;
}
for( int i = 1;i <= k;i ++ )
printf( "%c", s[( i - 1 ) % len + 1] );
return 0;
}
#698-Div.2
A. Nezzar and Colorful Balls
求出现次数最多的数,输入还保证递增,不说了
B. Nezzar and Lucky Number
将 x x x分裂成几个 d d d,发现在 d d d前面加的数都是 10 10 10的倍数
我就想能不能把 x x x的个位卡掉后,剩下的 10 10 10的倍数就随便塞到分裂的 d d d前面
这就转化为了同余问题
#include <cstdio>
int T, Q, d, x;
int main() {
scanf( "%d", &T );
while( T -- ) {
scanf( "%d %d", &Q, &d );
while( Q -- ) {
scanf( "%d", &x );
int i;
for( i = 1;i < 10;i ++ )
if( ( d * i ) % 10 == x % 10 ) break;
if( i * d > x ) printf( "NO\n" );
else printf( "YES\n" );
}
}
return 0;
}
D. Nezzar and Board
注意到 2 x − y 2x-y 2x−y这个操作翻译成数学意思,其实是在数轴上 y y y关于 x x x的对称点
操作可以无限下去,到最后肯定是两点之间的距离相等为止,否则一定会再次操作定位新的坐标点
而这个距离就是原数组二者之间的 g c d gcd gcd
这道题很需要数学的直觉把我是SB再见
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define int long long
#define maxn 200005
int T, n, k;
int MS[maxn];
int gcd( int x, int y ) {
if( x < y ) swap( x, y );
if( ! y ) return x;
else return gcd( y, x % y );
}
signed main() {
scanf( "%lld", &T );
while( T -- ) {
scanf( "%lld %lld", &n, &k );
for( int i = 1;i <= n;i ++ )
scanf( "%lld", &MS[i] );
sort( MS + 1, MS + n + 1 );
int g = MS[2] - MS[1];
for( int i = 3;i <= n;i ++ )
g = gcd( g, MS[i] - MS[i - 1] );
if( ( k - MS[1] ) % g ) printf( "NO\n" );
else printf( "YES\n" );
}
return 0;
}
GlobalRound-12
A. Avoid Trygub
直接字母排序输出
B. Balls of Steel
发现结果只有可能是1/-1
,如果第一次两个点无法合并那么后面就不可能通过中转点合并,因为点的位置会发生改变,样例三就是提醒这一点
C. Errich-Tac-Toe
将行列按
(
i
+
j
)
%
3
(i+j)\%3
(i+j)%3分类(染色),选择颜色中最小的特殊格子(属于某三个连续情况)个数
困难情况无非是还要按
X
/
O
X/O
X/O分成六类
easy version
#include <cstdio>
#define maxn 305
int T, n;
char s[maxn][maxn];
int cnt[5];
bool check( int i, int j ) {
if( s[i][j] != 'X' ) return 0;
if( j > 1 && s[i][j - 2] == 'X' && s[i][j - 1] == 'X' ) return 1;
if( j < n && s[i][j - 1] == 'X' && s[i][j + 1] == 'X' ) return 1;
if( j < n - 1 && s[i][j + 1] == 'X' && s[i][j + 2] == 'X' ) return 1;
if( i > 1 && s[i - 2][j] == 'X' && s[i - 1][j] == 'X' ) return 1;
if( i < n && s[i - 1][j] == 'X' && s[i + 1][j] == 'X' ) return 1;
if( i < n - 1 && s[i + 1][j] == 'X' && s[i + 2][j] == 'X' ) return 1;
return 0;
}
int main() {
scanf( "%d", &T );
while( T -- ) {
scanf( "%d", &n );
for( int i = 1;i <= n;i ++ )
scanf( "%s", s[i] + 1 );
cnt[0] = cnt[1] = cnt[2] = 0;
for( int i = 1;i <= n;i ++ )
for( int j = 1;j <= n;j ++ )
if( check( i, j ) ) cnt[( i + j ) % 3] ++;
int minn = 1e9, ip;
for( int i = 0;i < 3;i ++ )
if( cnt[i] < minn ) minn = cnt[i], ip = i;
for( int i = 1;i <= n;i ++ ) {
for( int j = 1;j <= n;j ++ )
if( check( i, j ) && ( ( i + j ) % 3 == ip ) )
printf( "O" );
else
printf( "%c", s[i][j] );
printf( "\n" );
}
}
return 0;
}
hard version
#include <cstdio>
#include <cstring>
#define maxn 305
int T, n;
char s[maxn][maxn];
int cnt[2][3];
void solve( int c1, int c0 ) {
for( int i = 1;i <= n;i ++ ) {
for( int j = 1;j <= n;j ++ )
if( ( i + j ) % 3 == c1 && s[i][j] == 'X' )
printf( "O" );
else if( ( i + j ) % 3 == c0 && s[i][j] == 'O' )
printf( "X" );
else
printf( "%c", s[i][j] );
printf( "\n" );
}
}
int main() {
scanf( "%d", &T );
again :
while( T -- ) {
scanf( "%d", &n );
int k = 0;
memset( cnt, 0, sizeof( cnt ) );
for( int i = 1;i <= n;i ++ ) {
scanf( "%s", s[i] + 1 );
for( int j = 1;j <= n;j ++ )
switch( s[i][j] ) {
case 'X' : {
cnt[1][( i + j ) % 3] ++;
k ++;
break;
}
case 'O' : {
cnt[0][( i + j ) % 3] ++;
k ++;
break;
}
}
}
for( int i = 0;i < 3;i ++ )
for( int j = 0;j < 3;j ++ )
if( i == j ) continue;
else if( cnt[1][i] + cnt[0][j] <= k / 3 ) {
solve( i, j );
goto again;
}
}
return 0;
}
D. Rating Compression
- 如果 k = 1 k=1 k=1,要求原序列是个排列
- 如果 k = n k=n k=n,要求序列有 1 1 1
- 如果
1
<
k
<
n
1<k<n
1<k<n,如果最后的结果为排列,则
1
1
1只能出现一次,发现如果
1
1
1不出现在
1
/
n
1/n
1/n位置上,就必定会出现
≥
2
≥2
≥2次
综上,得出规律,定义 l , r l,r l,r,要求对于 i i i一定出现在 l / r l/r l/r,递归下去
#include <cstdio>
#define maxn 300005
int T, n;
int a[maxn], cnt[maxn];
bool ans[maxn];
int main() {
scanf( "%d", &T );
while( T -- ) {
scanf( "%d", &n );
for( int i = 1;i <= n;i ++ )
cnt[i] = ans[i] = 0;
for( int i = 1;i <= n;i ++ )
scanf( "%d", &a[i] ), cnt[a[i]] ++;
for( int i = 1;i <= n;i ++ )
if( cnt[i] == 1 ) continue;
else goto pass;
ans[1] = 1;
pass :
ans[n] = cnt[1];
int l = 1, r = n;
for( int i = n - 1;i;i -- ) {
int nxt = n - i;
cnt[nxt] --;
if( ! cnt[nxt] && ( a[l] == nxt || a[r] == nxt ) && cnt[nxt + 1] ) {
if( a[l] == nxt ) l ++;
else r --;
ans[i] = 1;
}
else break;
}
for( int i = 1;i <= n;i ++ )
printf( "%d", ans[i] );
printf( "\n" );
}
return 0;
}
E. Capitalism
∣
a
u
−
a
v
∣
=
1
→
|a_u-a_v|=1\rightarrow
∣au−av∣=1→ 差分约束
→
\rightarrow
→ 图论最短路
确定方向的,相当于
1
≤
a
v
−
a
u
≤
1
1\le a_v-a_u\le 1
1≤av−au≤1,
(
u
→
v
)
=
1
,
(
v
→
u
)
=
1
(u\rightarrow v)=1,(v\rightarrow u)=1
(u→v)=1,(v→u)=1
未知方向的,相当于
−
1
≤
a
v
−
a
u
≤
1
-1\le a_v-a_u\le 1
−1≤av−au≤1,
(
u
→
v
)
=
(
v
→
u
)
=
1
(u\rightarrow v)=(v\rightarrow u)=1
(u→v)=(v→u)=1
不能出现负环,嫉妒冲突了
而且一定是个二部图
a
u
≠
a
v
a_u≠a_v
au=av,可以用二分图染色判断
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define maxn 205
int n, m;
int dis[maxn][maxn];
bool vis[maxn];
int c[maxn];
int Fabs( int x ) {
return x < 0 ? -x : x;
}
void dfs( int now ) {
vis[now] = 1;
for( int i = 1;i <= n;i ++ )
if( i != now && dis[now][i] <= n ) {
if( ! vis[i] ) c[i] = c[now] ^ 1, dfs( i );
else if( c[i] == c[now] ) {
printf( "NO\n" );
exit( 0 );
}
}
}
int main() {
scanf( "%d %d", &n, &m );
memset( dis, 0x3f, sizeof( dis ) );
for( int i = 1;i <= n;i ++ ) dis[i][i] = 0;
for( int i = 1, u, v, w;i <= m;i ++ ) {
scanf( "%d %d %d", &u, &v, &w );
dis[u][v] = 1, dis[v][u] = w ? -1 : 1;
}
dfs( 1 );
for( int k = 1;k <= n;k ++ )
for( int i = 1;i <= n;i ++ )
for( int j = 1;j <= n;j ++ )
dis[i][j] = min( dis[i][j], dis[i][k] + dis[k][j] );
for( int i = 1;i <= n;i ++ )
if( dis[i][i] < 0 ) return ! printf( "NO\n" );
int ans = -1, pos;
for( int i = 1;i <= n;i ++ )
for( int j = 1;j <= n;j ++ )
if( Fabs( dis[i][j] ) > ans ) ans = Fabs( dis[i][j] ), pos = i;
printf( "YES\n%d\n", ans );
for( int i = 1;i <= n;i ++ )
printf( "%d ", dis[pos][i] + n );
return 0;
}
F. The Struggling Contestant
不相邻的最小,显然要将相邻的数绑在一起移动
设 k k k表示连续相同的对数, f ( i ) f(i) f(i)表示 i i i成为端点的次数,可行解的条件为 max ( f ( i ) ) ≤ k + 2 \max\bigg(f(i)\bigg)\le k+2 max(f(i))≤k+2
假设有 k + 1 k+1 k+1段,那么有 2 k + 2 2k+2 2k+2个端点,除掉最左边和最右边的两个端点
还有 2 k 2k 2k个端点,每两个端点只能选一个,有 k k k个,加在一起最多也就是 k + 2 k+2 k+2
因此最大值为 k + 2 k+2 k+2
接下来证明可以构造出解来
设 x x x为出现次数最多的数, y ( ≠ x ) y(≠x) y(=x)为两个段的端点,进行合并,则 k , f ( x ) , f ( y ) k,f(x),f(y) k,f(x),f(y)均 − 1 -1 −1,仍满足 f ( x ) , f ( y ) ≤ k + 2 f(x),f(y)\le k+2 f(x),f(y)≤k+2的关系式
对于 z ( ≠ x ≠ y ) z(≠x≠y) z(=x=y),合并前 f ( z ) ≤ 2 k + 2 − f ( x ) ≤ 2 k + 2 − f ( z ) ⇒ f ( z ) ≤ k + 1 f(z)\le 2k+2-f(x)\le 2k+2-f(z)\Rightarrow f(z)\le k+1 f(z)≤2k+2−f(x)≤2k+2−f(z)⇒f(z)≤k+1合并后 k − 1 k-1 k−1,仍满足 f ( z ) ≤ k + 2 f(z)\le k+2 f(z)≤k+2
这样每次将 k k k减一至零,一共 k k k次操作,整个过程都是 f ( ) ≤ k + 2 f()\le k+2 f()≤k+2
现在考虑 max ( f ( x ) ) > k + 2 \max\bigg(f(x)\bigg)>k+2 max(f(x))>k+2的情况,想办法切割成 ≤ \le ≤
如果在切割时包含了 x x x,那么 f ( x ) , k f(x),k f(x),k均增加,情况并未发生改变
所以需要在 y ( ≠ x ) , z ( ≠ x ) y(≠x),z(≠x) y(=x),z(=x)两个点之间进行切割,这样会导致 k k k增加
重复这样的操作直到变化,操作次数为 max ( f ( x ) ) − ( k + 2 ) \max\bigg(f(x)\bigg)-(k+2) max(f(x))−(k+2)
#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 100005
int T, n;
int a[maxn], cnt[maxn], f[maxn];
int main() {
scanf( "%d", &T );
while( T -- ) {
scanf( "%d",&n );
for( int i = 1;i <= n;i ++ )
f[i] = cnt[i] = 0;
int k = 0;
for( int i = 1;i <= n;i ++ ) {
scanf( "%d", &a[i] );
cnt[a[i]] ++;
if( a[i] == a[i - 1] ) {
k ++;
f[a[i]] += 2;
}
}
f[a[1]] ++, f[a[n]] ++;
int max_cnt = 0, max_f = 0;
for( int i = 1;i <= n;i ++ ) {
max_cnt = max( max_cnt, cnt[i] );
max_f = max( max_f, f[i] );
}
if( ( max_cnt << 1 ) > n + 1 ) printf( "-1\n" );
else printf( "%d\n", k + max( 0, max_f - ( k + 2 ) ) );
}
return 0;
}
G. Communism
去掉6个字母恰好只剩20个,刚好是可以状压的范围 用意令人深思
令 l i : l_i: li:字符集为 i i i的最早出现位置, r i : r_i: ri:字符集为 i i i的最晚出现位置, c n t i : cnt_i: cnti:字符集为 i i i出现次数
定义 d p s : dp_s: dps:字符集为 s s s时,能否被转移到
-
d p 0 = 1 dp_0=1 dp0=1
-
当 s = { i } s=\{i\} s={i}只有一个字符时,如果满足 k × ( r i − l i + 1 ) ≤ c n t i ⇒ d p s = 1 k\times(r_i-l_i+1)\le cnt_i\Rightarrow dp_s=1 k×(ri−li+1)≤cnti⇒dps=1
-
对于某一个字符 i i i,一开始无法操作,如果可以先操作 s − i s-i s−i剩下字符再操作 i i i就可以
[ i ∈ s ] ⋂ [ d p s − x = 1 ] ⋂ [ k × ( r s − l s + 1 ) ≤ c n t s ] ⇒ d p s = 1 [i∈s]\bigcap[dp_{s-x}=1]\bigcap[k\times(r_s-l_s+1)\le cnt_s]\Rightarrow dp_s=1 [i∈s]⋂[dps−x=1]⋂[k×(rs−ls+1)≤cnts]⇒dps=1
-
当字符集为两不相交的字符集的并,且两字符集都可以操作,那么合并后的字符集也同样可以操作
[ s = i ⋃ j ] ⋂ [ i ⋂ j = ∅ ] ⋂ [ d p i = d p j = 1 ] ⇒ d p s = 1 [s=i\bigcup j]\bigcap[i\bigcap j=\empty]\bigcap[dp_i=dp_j=1]\Rightarrow dp_s=1 [s=i⋃j]⋂[i⋂j=∅]⋂[dpi=dpj=1]⇒dps=1
但是因为性质四,涉及子集枚举, O ( 3 20 ) O(3^{20}) O(320),考虑优化
性质:当且仅当两个字符集的管辖区间 l i , r i l_i,r_i li,ri不相交,才需要考虑这样的转移
因为如果区间有相交,那么一定是性质三的转移不差于性质四转移
两段相交区间,唯一变的就是 r − l + 1 r-l+1 r−l+1,变小,则可以适应更大的 k k k,更容易转移,所以选择一个吃掉另一个再转移
那么这个优化就可以按照 l l l排序,前缀和优化
最后如果 d p U ⨁ i = 1 dp_{U\bigoplus i}=1 dpU⨁i=1,则该字符是可以被操作成整字符串的
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define maxn 5005
#define alpha 150
#define maxs 1 << 20
int n, a, b;
bool dp[maxs];
char S[maxn], MS[alpha];
int id[alpha], l[maxs], r[maxs], cnt[maxs], s[maxn];
bool cmp( int x, int y ) {
return l[1 << x] < l[1 << y];
}
int lowbit( int x ) {
return x & ( -x );
}
int main() {
int n, a, b;
scanf( "%d %d %d %s", &n, &a, &b, S + 1 );
int ip = -1, U = 0;
for( char i = 'a';i <= 'z';i ++ )
if( i == 't' || i == 'r' || i == 'y' || i == 'g' || i == 'u' || i == 'b' )
continue;
else
id[i] = ++ ip, MS[ip] = i;
for( int i = 1;i <= n;i ++ )
s[i] = id[S[i]], U |= ( 1 << s[i] );
for( int i = 0;i < 20;i ++ )
id[i] = i, l[1 << i] = n;
for( int i = 1;i <= n;i ++ ) {
l[1 << s[i]] = min( i, l[1 << s[i]] );
r[1 << s[i]] = i;
cnt[1 << s[i]] ++;
}
sort( id, id + 20, cmp );
for( int i = 0;i < maxs;i ++ )
if( i ^ lowbit( i ) ) {
l[i] = min( l[i ^ lowbit( i )], l[lowbit( i )] );
r[i] = max( r[i ^ lowbit( i )], r[lowbit( i )] );
cnt[i] = cnt[i ^ lowbit( i )] + cnt[lowbit( i )];
}
dp[0] = 1;
for( int i = 1;i < maxs;i ++ ) {
if( ( i & U ) != i ) continue;
if( ( r[i] - l[i] + 1 ) * a <= cnt[i] * b ) {
if( i == lowbit( i ) ) dp[i] = 1;
else {
for( int j = 0;j < 20;j ++ )
if( 1 << j & i ) dp[i] |= dp[1 << j ^ i];
}
}
if( ! dp[i] ) {
int k = 0;
for( int j = 0;j < 20;j ++ ) {
k = ( 1 << id[j] | k ) & i;
dp[i] |= dp[i ^ k] & dp[k];
}
}
}
int ans = 0;
for( int i = 0;i < 20;i ++ )
if( dp[1 << i ^ U] ) ans ++;
printf( "%d", ans );
for( int i = 0;i < 20;i ++ )
if( dp[1 << i ^ U] )
printf( " %c", MS[i] );
return 0;
}
Educational-Round-111
C. Manhattan Subarrays
曼哈顿距离,发现若 [ l , r ] [l,r] [l,r]内存在三个 递增 / 递减 / 两平加一 的点,则一定不是好区间
因此发现,好区间的长度最多不超过4
,直接暴力可做
#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 200005
int T, n, ans;
int a[maxn];
int Fabs( int x ) {
return x < 0 ? -x : x;
}
int dis( int i, int j ) {
return Fabs( i - j ) + Fabs( a[i] - a[j] );
}
void calc( int l, int r ) {
for( int i = l;i <= r;i ++ )
for( int j = i + 1;j <= r;j ++ )
for( int k = j + 1;k <= r;k ++ )
if( dis( i, j ) + dis( j, k ) == dis( i, k ) ) return;
ans ++;
}
int main() {
scanf( "%d", &T );
while( T -- ) {
scanf( "%d", &n );
for( int i = 1;i <= n;i ++ )
scanf( "%d", &a[i] );
ans = 0;
for( int i = 1;i <= n;i ++ )
for( int j = i;j <= min( n, i + 3 );j ++ )
calc( i, j );
printf( "%d\n", ans );
}
return 0;
}
D. Excellent Arrays
-
a i + a j = i + j ⇔ a i − i = − ( a j − j ) a_i+a_j=i+j\Leftrightarrow a_i-i=-(a_j-j) ai+aj=i+j⇔ai−i=−(aj−j)
定义 w i = a i − i ⇒ a i − i = w i → w i = − w j w_i=a_i-i\Rightarrow a_i-i=w_i\rightarrow w_i=-w_j wi=ai−i⇒ai−i=wi→wi=−wj
-
{ a i + a j = i + j a j + a k = j + k a i + a k = i + k ⇒ { w i = − w j w j = − w k w i = − w k \begin{cases} a_i+a_j=i+j\\ a_j+a_k=j+k\\ a_i+a_k=i+k\\ \end{cases} \Rightarrow \begin{cases} w_i=-w_j w_j=-w_k w_i=-w_k \end{cases} ⎩⎪⎨⎪⎧ai+aj=i+jaj+ak=j+kai+ak=i+k⇒{wi=−wjwj=−wkwi=−wk
若想要三个中任意两个都能配对,当且仅当 a i = i a_i=i ai=i,但不满足好序列要求
因此所有数只能分成两部分,不同部分间彼此配对
-
配对的数量最大化,显然是一半的数为 w w w,另外一半的数为 − w -w −w
这样配对数量最多,可以达到 ( n 2 ) 2 (\frac{n}{2})^2 (2n)2级(正方形面积问题)
F ( a ) = ⌊ n 2 ⌋ ⋅ ⌈ n 2 ⌉ F(a)=\lfloor\frac{n}{2}\rfloor·\lceil{\frac{n}{2}\rceil} F(a)=⌊2n⌋⋅⌈2n⌉
-
但是有的 i i i,可能只能是 w w w类型,有的可能只能是 − w -w −w类型, 有的二者皆可
-
定义 h a l f = ⌊ n 2 ⌋ half=\lfloor\frac{n}{2}\rfloor half=⌊2n⌋,不同范围 w w w的计数(假设 w > 0 w>0 w>0)
有两个不等式
{ l ≤ a i = w i + i ≤ r l ≤ a i = − w i + i ≤ r ⇒ { l − i ≤ w i ≤ r − i i − r ≤ w i ≤ i − l \begin{cases} l\le a_i=w_i+i\le r\\ l\le a_i=-w_i+i\le r \end{cases} \Rightarrow \begin{cases} l-i\le w_i\le r-i\\ i-r\le w_i\le i-l \end{cases} {l≤ai=wi+i≤rl≤ai=−wi+i≤r⇒{l−i≤wi≤r−ii−r≤wi≤i−l-
如果 max ( l − i , i − r ) ≤ w i ≤ min ( r − i , i − l ) ⇒ w ≤ min ( r − n , 1 − l ) \max(l-i,i-r)\le w_i\le \min(r-i,i-l)\Rightarrow w\le \min(r-n,1-l) max(l−i,i−r)≤wi≤min(r−i,i−l)⇒w≤min(r−n,1−l),则整个 [ l , r ] [l,r] [l,r]都是二者类型
- n = 2 k → C n h a l f n=2k\rightarrow C_{n}^{half} n=2k→Cnhalf
- n = 2 k + 1 → C n h a l f + C n h a l f + 1 n=2k+1\rightarrow C_{n}^{half}+C_{n}^{half+1} n=2k+1→Cnhalf+Cnhalf+1
-
如果 w > min ( r − n , 1 − l ) w>\min(r-n,1-l) w>min(r−n,1−l),则 ∀ i ∈ [ 1 , L ) a i \forall_{i∈[1,L)}a_i ∀i∈[1,L)ai只能是 w w w类型, ∀ i ∈ ( R , n ] a i \forall_{i∈(R,n]}a_i ∀i∈(R,n]ai只能是 − w -w −w类型
{ L = min ( 1 , l + w ) ; R = max ( n , r − w ) } \bigg\{L=\min(1,l+w);R=\max(n,r-w)\bigg\} {L=min(1,l+w);R=max(n,r−w)}
R − L + 1 R-L+1 R−L+1个则是自由二者皆可
枚举 w w w,当 L , R L,R L,R冲突时
break
- n = 2 k → C R − L + 1 h a l f − ( L − 1 ) n=2k\rightarrow C_{R-L+1}^{half-(L-1)} n=2k→CR−L+1half−(L−1)
- n = 2 k + 1 → C R − L + 1 h a l f − ( L − 1 ) + C R − L + 1 h a l f − ( L − 1 ) + 1 n=2k+1\rightarrow C_{R-L+1}^{half-(L-1)}+C_{R-L+1}^{half-(L-1)+1} n=2k+1→CR−L+1half−(L−1)+CR−L+1half−(L−1)+1
-
时间复杂度 O ( n log M ) O(n\log M) O(nlogM)
#include <cstdio>
#include <iostream>
using namespace std;
#define mod 1000000007
#define int long long
#define maxn 200000
int fac[maxn << 1], inv[maxn << 1];
int T, n, l, r;
int qkpow( int x, int y ) {
int ans = 1;
while( y ) {
if( y & 1 ) ans = ans * x % mod;
x = x * x % mod;
y >>= 1;
}
return ans;
}
int C( int n, int m ) {
if( n < 0 || n < m || m < 0 ) return 0;
else return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
signed main() {
fac[0] = inv[0] = 1;
for( int i = 1;i <= maxn;i ++ )
fac[i] = fac[i - 1] * i % mod;
inv[maxn] = qkpow( fac[maxn], mod - 2 );
for( int i = maxn - 1;i;i -- )
inv[i] = inv[i + 1] * ( i + 1 ) % mod;
scanf( "%lld", &T );
while( T -- ) {
scanf( "%lld %lld %lld", &n, &l, &r );
int k = min( 1 - l, r - n ), half = n >> 1, ans = 0;
if( n & 1 )
ans = ( k * C( n, half ) % mod + k * C( n, half + 1 ) % mod ) % mod;
else
ans = k * C( n, half );
while( ++ k ) {
int L = max( 1ll, l + k ), R = min( r - k, n ), len = R - L + 1;
if( len < 0 ) break;
else
if( n & 1 )
ans = ( ans + C( len, half - ( L - 1 ) ) + C( len, half - ( L - 1 ) + 1 ) ) % mod;
else
ans = ( ans + C( len, half - ( L - 1 ) ) ) % mod;
}
printf( "%lld\n", ans );
}
return 0;
}
E. Stringforces
-
二分答案长度
len
-
check
长度,想要在 n n n以内使得前 k k k个罗马小写字符都连续出现至少len
个转化为让前 k k k个小写字符都连续出现至少一次的至少
len
个的最短长度 -
设 d p s : dp_s: dps: 已经构造的字符集为
s
,字符集内每个字符都满足要求的最短长度 -
设 p o s i , j : pos_{i,j}: posi,j: 以
i
往后的某个位置开始填连续len
个j
字符的结束位置需要保证,整段除了
?
就是j
,定义 l a s t i : last_i: lasti:i
字符上次出现位置,枚举字符判断是否在段内即可 -
转移: d p s ∣ 1 < < j = min { p o s d p s + 1 , j } dp_{s|1<<j}=\min\{pos_{dp_s+1,j}\} dps∣1<<j=min{posdps+1,j}
-
最后保证长度 ≤ n \le n ≤n即可
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define inf 0x7f7f7f7f
#define maxn 200005
#define alpha 17
int n, k, S;
char s[maxn];
int dp[1 << alpha], last[maxn];
int pos[maxn][alpha];
bool check( int len ) {
memset( pos, 0x7f, sizeof( pos ) );
memset( last, 0x7f, sizeof( last ) );
for( int i = n;i;i -- ) {
if( s[i] != '?' ) last[s[i] - 'a'] = i; else;
for( int j = 0;j < k;j ++ ) {
pos[i][j] = ( i + len - 1 <= n ) ? i + len - 1 : pos[i + 1][j];
for( int w = 0;w < k;w ++ )
if( j == w ) continue;
else
if( last[w] <= i + len - 1 ) {
pos[i][j] = pos[i + 1][j];
break;
} else;
}
}
memset( dp, 0x7f, sizeof( dp ) );
dp[0] = 0;
for( int i = 0;i < S;i ++ )
for( int j = 0;j < k;j ++ )
if( ( 1 << j & i ) || dp[i] == inf ) continue;
else dp[1 << j | i] = min( dp[1 << j | i], pos[dp[i] + 1][j] );
return dp[S - 1] <= n;
}
int main() {
scanf( "%d %d %s", &n, &k, s + 1 );
S = 1 << k;
int l = 0, r = n / k, ans = 0;
while( l <= r ) {
int mid = ( l + r ) >> 1;
if( check( mid ) ) ans = mid, l = mid + 1;
else r = mid - 1;
}
printf( "%d\n", ans );
return 0;
}
F. Jumping Around
boruvka
最小生成树
既有kruskal
的并查集合并,又有prim
的扩展,因而正确性得到保证
- 初始化,每个点为一个联通块
- 根据题目选择数据结构,快速找到两个联通块之间的最大/最小边权
- 并查集按秩合并,建图
- 利用图的
dfs
树上的lca
等信息求解
对于此题,两点互达的最小需要
k
k
k即为边权
具体代码:
枚举联通块,set
维护点,对于联通块内的每个点
先把同联通块的点扔出去
set
查找距离点最近的前后点(隶属不同联通块)
边权
∣
∣
a
i
−
a
x
∣
−
d
∣
\big||a_i-a_x|-d\big|
∣∣∣ai−ax∣−d∣∣
建图,从
s
s
s开始走dfs
树,记录到每个点途经的最大值
那就是最小生成树的瓶颈边,判断给定的
k
k
k与该边的关系即可
#include <set>
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define inf 0x7f7f7f7f
#define maxn 1000005
struct node {
int u, v, w;
node(){}
node( int U, int V, int W ) {
u = U, v = V, w = W;
}
};
set < int > pos;
set < int > :: iterator it;
vector < int > MS[maxn];
vector < pair < int, int > > G[maxn];
int n, Q, s, d;
int ans[maxn], x[maxn], f[maxn], id[maxn];
int Fabs( int x ) {
return x < 0 ? -x : x;
}
bool operator < ( node x, node y ) {
return x.w < y.w;
}
void dfs( int u, int fa, int val ) {
ans[u] = val;
for( int i = 0;i < G[u].size();i ++ ) {
int v = G[u][i].first;
if( v == fa ) continue;
else dfs( v, u, max( val, G[u][i].second ) );
}
}
bool merge( int u, int v ) {
u = f[u], v = f[v];
if( u == v ) return 0;
else {
if( MS[u].size() < MS[v].size() ) swap( u, v );
for( int i = 0;i < MS[v].size();i ++ )
f[MS[v][i]] = u, MS[u].push_back( MS[v][i] );
MS[v].clear();
return 1;
}
}
int main() {
scanf( "%d %d %d %d", &n, &Q, &s, &d );
for( int i = 1;i <= n;i ++ ) {
scanf( "%d", &x[i] );
MS[i].push_back( i );
f[i] = id[x[i]] = i;
pos.insert( x[i] );
}
int cnt = n;
while( cnt > 1 ) {
vector < node > edge;
for( int i = 1;i <= n;i ++ ) {
if( ! MS[i].size() ) continue;
else {
for( int j : MS[i] ) pos.erase( x[j] );
node minn = node( 0, 0, inf );
for( int j : MS[i] )
for( int k : { -d, d } ) {
it = pos.lower_bound( x[j] + k );
if( it != pos.end() )
minn = min( minn, node( j, id[*it], Fabs( Fabs( x[j] - ( *it ) ) - d ) ) );
if( it != pos.begin() )
it --, minn = min( minn, node( j, id[*it], Fabs( Fabs( x[j] - ( *it ) ) - d ) ) );
}
for( int j : MS[i] ) pos.insert( x[j] );
if( minn.w == inf ) continue;
else edge.push_back( minn );
}
}
for( int i = 0;i < edge.size();i ++ )
if( merge( edge[i].u, edge[i].v ) ) {
-- cnt;
G[edge[i].u].push_back( make_pair( edge[i].v, edge[i].w ) );
G[edge[i].v].push_back( make_pair( edge[i].u, edge[i].w ) );
}
}
dfs( s, 0, 0 );
while( Q -- ) {
int i, k;
scanf( "%d %d", &i, &k );
if( ans[i] <= k ) printf( "Yes\n" );
else printf( "No\n" );
}
return 0;
}
Educational-Round-106
C. Minimum Grid Path
先往上走和先往右走是等价的,因为两个方向上路径综合是相同的 n n n(假设先往右走)
直接枚举有多少段,按奇偶分开算,那么有 ⌈ i 2 ⌉ \lceil\frac{i}{2}\rceil ⌈2i⌉段都是向右走, ⌊ n 2 ⌋ \lfloor\frac{n}{2}\rfloor ⌊2n⌋都是向上走
贪心的有,花费最小的尽可能多走长度,其余的花费就只让走一步就拐即可
#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
#define maxn 100005
int T, n;
int c[maxn], ans[maxn], sum[maxn];
signed main() {
scanf( "%lld", &T );
while( T -- ) {
scanf( "%lld", &n );
for( int i = 1;i <= n;i ++ )
scanf( "%lld", &c[i] );
sum[1] = c[1], sum[2] = c[2];
for( int i = 3;i <= n;i ++ )
sum[i] = sum[i - 2] + c[i];
int minn = c[1]; ans[1] = c[1] * n;
for( int i = 3;i <= n;i += 2 )
if( c[i] > minn )
ans[i] = ans[i - 2] - minn + c[i];
else {
ans[i] = sum[i - 2] + c[i] * ( n - i / 2 );
minn = c[i];
}
minn = c[2]; ans[2] = c[2] * n;
for( int i = 4;i <= n;i += 2 )
if( c[i] > minn )
ans[i] = ans[i - 2] - minn + c[i];
else {
ans[i] = sum[i - 2] + c[i] * ( n - i / 2 + 1 );
minn = c[i];
}
int ret = 1e18;
for( int i = 2;i <= n;i ++ )
ret = min( ret, ans[i] + ans[i - 1] );
printf( "%lld\n", ret );
}
return 0;
}
D. The Number of Pairs
- 令
a
=
A
g
,
b
=
B
g
⇒
(
A
,
B
)
=
1
a=Ag,b=Bg\Rightarrow (A,B)=1
a=Ag,b=Bg⇒(A,B)=1,改写式子
→
c
∗
A
∗
B
∗
g
=
x
+
d
∗
g
\rightarrow c*A*B*g=x+d*g
→c∗A∗B∗g=x+d∗g
- 若要等式存在,第一个要满足的条件就是
g
∣
x
g\bigg|x
g∣∣∣∣x
- O ( x ) O(\sqrt{x}) O(x)预处理出 x x x的所有因数
- 枚举因数
g
g
g
- 反解出
A
B
=
x
g
+
d
c
AB=\frac{\frac{x}{g}+d}{c}
AB=cgx+d
- 同样因为 A , B A,B A,B为整数,第二个要满足的条件就是 c ∣ ( x g + d ) c\bigg|(\frac{x}g{+d)} c∣∣∣∣(gx+d)
- 知道 A ∗ B A*B A∗B后,答案就是 A ∗ B A*B A∗B的 2 质 因 子 个 数 2^{质因子个数} 2质因子个数,可以通过欧筛提前预处理
- 反解出
A
B
=
x
g
+
d
c
AB=\frac{\frac{x}{g}+d}{c}
AB=cgx+d
- 若要等式存在,第一个要满足的条件就是
g
∣
x
g\bigg|x
g∣∣∣∣x
#include <cstdio>
#include <vector>
using namespace std;
#define maxn 20000000
int cnt[maxn + 5];
bool vis[maxn + 5];
void sieve() {
for( int i = 2;i <= maxn;i ++ )
if( ! vis[i] )
for( int j = i;j <= maxn;j += i )
vis[j] = 1, cnt[j] ++;
}
int main() {
sieve();
int T, c, d, x;
scanf( "%d", &T );
while( T -- ) {
scanf( "%d %d %d", &c, &d, &x );
vector < int > G;
for( int i = 1;i * i <= x;i ++ )
if( x % i == 0 ) {
G.push_back( i );
if( i * i != x ) G.push_back( x / i );
}
long long ans = 0;
for( int i = 0;i < G.size();i ++ ) {
int g = G[i];
if( ( x / g + d ) % c ) continue;
else ans += ( 1ll << cnt[( ( x / g ) + d ) / c] );
}
printf( "%lld\n", ans );
}
return 0;
}
E. Chaotic Merge
设 d p i , j , k , 0 / 1 : dp_{i,j,k,0/1}: dpi,j,k,0/1: 字符串 s s s的指针指向 i i i,字符串 t t t的指针指向 j j j( s i , t j s_i,t_j si,tj还未被操作),是否 s , t s,t s,t中的字符都出现过的情况 k k k(有 4 4 4种),合并串当前位是 0 − s i − 1 / 1 − t j − 1 0-s_{i-1}/1-t_{j-1} 0−si−1/1−tj−1的方案数
对应的有四种转移情况,看代码就行
但是题目是要求 [ l , r ] [l,r] [l,r]区间的,其实从 d p i + 1 , j , 1 , 0 = d p i , j + 1 , 2 , 1 = 1 dp_{i+1,j,1,0}=dp_{i,j+1,2,1}=1 dpi+1,j,1,0=dpi,j+1,2,1=1的初始化就代表了从 i , j i,j i,j开始的字符串转移
#include <cstdio>
#include <cstring>
#define maxn 1005
#define int long long
#define mod 998244353
char s[maxn], t[maxn];
int dp[maxn][maxn][4][2];
signed main() {
scanf( "%s %s", s, t );
int n = strlen( s ), m = strlen( t ), ans = 0;
for( int i = 0;i <= n;i ++ )
for( int j = 0;j <= m;j ++ ) {
if( i < n ) dp[i + 1][j][1][0] = 1;
if( j < m ) dp[i][j + 1][2][1] = 1;
for( int k = 0;k < 4;k ++ ) {
if( 0 < i && i < n && s[i - 1] != s[i] )
dp[i + 1][j][k | 1][0] = ( dp[i + 1][j][k | 1][0] + dp[i][j][k][0] ) % mod;
if( 0 < j && i < n && t[j - 1] != s[i] )
dp[i + 1][j][k | 1][0] = ( dp[i + 1][j][k | 1][0] + dp[i][j][k][1] ) % mod;
if( 0 < i && j < m && s[i - 1] != t[j] )
dp[i][j + 1][k | 2][1] = ( dp[i][j + 1][k | 2][1] + dp[i][j][k][0] ) % mod;
if( 0 < j && j < m && t[j - 1] != t[j] )
dp[i][j + 1][k | 2][1] = ( dp[i][j + 1][k | 2][1] + dp[i][j][k][1] ) % mod;
}
ans = ( ans + dp[i][j][3][0] + dp[i][j][3][1] ) % mod;
}
printf( "%lld\n", ans );
return 0;
}
F. Diameter Cuts
设 d p u , i : u dp_{u,i}:u dpu,i:u子树内经过 u u u的最长路径为 i i i,枚举每一个儿子 v v v转移
-
断掉与儿子的边,枚举儿子的最长路径 j j j
d p u , i = ∑ j ≤ i d p u , i ⋅ d p v , j dp_{u,i}=\sum_{j\le i}dp_{u,i}·dp_{v,j} dpu,i=∑j≤idpu,i⋅dpv,j
-
不断边,那么就是 u u u某两个儿子最长路径拼起来
d p u , i + j + 1 = ∑ j ( i + j + 1 ≤ k ) d p u , i ⋅ d p v , j dp_{u,i+j+1}=\sum_{j(i+j+1\le k)}dp_{u,i}·dp_{v,j} dpu,i+j+1=∑j(i+j+1≤k)dpu,i⋅dpv,j
每次转移枚举到儿子最长路径即可,时间复杂度 O ( n 2 ) O(n^2) O(n2)
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define int long long
#define mod 998244353
#define maxn 5005
vector < int > G[maxn];
int n, k;
int dp[maxn][maxn];
int dfs( int u, int fa ) {
dp[u][0] = 1;
int dep = 0, dep_son;
for( int v : G[u] ) {
if( v == fa ) continue;
else dep_son = dfs( v, u );
vector < int > MS( max( dep, dep_son + 1 ) + 1 );
for( int i = 0;i <= dep;i ++ )
for( int j = 0;j <= dep_son;j ++ ) {
if( i + j + 1 <= k )
MS[max( i, j + 1 )] = ( MS[max( i, j + 1 )] + dp[u][i] * dp[v][j] ) % mod;
if( i <= k && j <= k )
MS[i] = ( MS[i] + dp[u][i] * dp[v][j] ) % mod;
}
dep = max( dep, dep_son + 1 );
for( int i = 0;i <= dep;i ++ )
dp[u][i] = MS[i];
}
return dep;
}
signed main() {
scanf( "%lld %lld", &n, &k );
for( int i = 1, u, v;i < n;i ++ ) {
scanf( "%lld %lld", &u, &v );
G[u].push_back( v );
G[v].push_back( u );
}
dfs( 1, 0 );
int ans = 0;
for( int i = 0;i <= k;i ++ )
ans = ( ans + dp[1][i] ) % mod;
printf( "%lld\n", ans );
return 0;
}
G. Graph Coloring
图一定是若干个偶环和若干条路径构成在一起
最小解的构造相当于对于一条路径/环的染色就是欧拉路径/回路的黑白染色法
环肯定可以黑白染色,删去,剩下了一张森林网络
对于一条路径而言,黑白交替染色后就是两个端点产生了贡献(度数为 1 1 1的点)
因为强制在线那么就只能真的加边,考虑在若干条路径间的加边情况
-
两个点都不是端点
那么直接两个点产生了一条新的路径
-
只有一个点是端点
直接把另一个点接上端点成为路径的新端点
-
两个点都是端点
但此时要注意两个端点的边染色是否相同,必须相同两个端点间的连边才能染成相反的颜色
如果不同,直接翻转某一个端点的路径即可,个数是不会发生变化的
用 s u m x , 0 / i sum_{x,0/i} sumx,0/i表示 0 − b l u e / 1 − r e d 0-blue/1-red 0−blue/1−red的 x x x路径 h a s h hash hash答案
上述染色问题的过程,带权并查集简直是专攻
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define int long long
#define mod 998244353
#define maxn 400005
vector < int > cnt;
int n, m, Q, ans;
int f[maxn], c[maxn], to[maxn], bit[maxn];
int sum[maxn][2];
int find( int x ) {
if( f[x] == x ) return x;
if( f[f[x]] == f[x] ) return f[x];
int fa = find( f[x] );
c[x] ^= c[f[x]];
return f[x] = fa;
}
int color( int x ) {
if( x == f[x] ) return c[x];
int fa = find( x );
return c[x] ^ c[fa];
}
void reverse( int x ) {
x = find( x );
ans = ( ans - sum[x][c[x]] + mod ) % mod;
c[x] ^= 1;
ans = ( ans + sum[x][c[x]] ) % mod;
}
void merge( int x, int y ) {
x = find( x ), y = find( y );
if( x == y ) return;
ans = ( ans - sum[x][c[x]] + mod ) % mod;
ans = ( ans - sum[y][c[y]] + mod ) % mod;
c[x] ^= c[y], f[x] = y;
sum[y][0] = ( sum[y][0] + sum[x][c[x]] ) % mod;
sum[y][1] = ( sum[y][1] + sum[x][! c[x]] ) % mod;
ans = ( ans + sum[y][c[y]] ) % mod;
}
void link( int x, int y, int id ) {
sum[id][1] = bit[id] = ( bit[id - 1] << 1 ) % mod, f[id] = id;//起初默认id边颜色为blue(0)
if( ! to[x] && ! to[y] ) {//两个都不是端点 新增一条路径
reverse( id );
to[x] = to[y] = id;
}
else if( ! to[x] || ! to[y] ) {//只有一个端点直接连起来
if( ! to[x] ) swap( x, y );
if( ! color( to[x] ) ) reverse( id );//端点边颜色为blue那么新连边就为red(1)
merge( to[x], id );
to[x] = 0, to[y] = id;
}
else {//两个都是端点
if( color( to[x] ) != color( to[y] ) ) reverse( to[x] );
//必须两端点边颜色相同 其之间的两边才能是另一种颜色 否则需要先翻转其中一条路径
if( ! color( to[x] ) ) reverse( id );
merge( to[x], id ), merge( to[y], id );
to[x] = to[y] = 0;
}
}
signed main() {
int n1, n2; bit[0] = 1;
scanf( "%lld %lld %lld", &n1, &n2, &m );
for( int i = 1, u, v;i <= m;i ++ ) {
scanf( "%lld %lld", &u, &v );
link( u, v + n1, i );
}
scanf( "%lld", &Q );
while( Q -- ) {
int opt, u, v;
scanf( "%lld", &opt );
if( opt & 1 ) {
scanf( "%lld %lld", &u, &v );
link( u, v + n1, ++ m );
printf( "%lld\n", ans );
}
else {
cnt.clear();
for( int i = 1;i <= m;i ++ )
if( color( i ) ) cnt.push_back( i );
printf( "%lld ", cnt.size() );
for( int i = 0;i < cnt.size();i ++ )
printf( "%lld ", cnt[i] );
printf( "\n" );
}
fflush( stdout );
}
return 0;
}
AtCoder
ARC-122
A - Many Formulae
考虑从前/从后递推到
i
i
i时,
i
i
i前面符号为+/-
的情况数,乘上相应的代价即可
#include <cstdio>
#define int long long
#define mod 1000000007
#define maxn 100005
int n;
int a[maxn];
int f[maxn][2], g[maxn][2];
signed main() {
scanf( "%lld", &n );
for( int i = 1;i <= n;i ++ )
scanf( "%lld", &a[i] );
f[1][1] = 1;
for( int i = 2;i <= n;i ++ ) {
f[i][0] = f[i - 1][1];
f[i][1] = ( f[i - 1][0] + f[i - 1][1] ) % mod;
}
g[n][0] = g[n][1] = 1;
for( int i = n - 1;i;i -- ) {
g[i][0] = g[i + 1][1];
g[i][1] = ( g[i + 1][0] + g[i + 1][1] ) % mod;
}
g[1][0] = 0;
int ans = 0;
for( int i = 1;i <= n;i ++ )
ans = ( ans - a[i] * f[i][0] % mod * g[i][0] % mod + a[i] * f[i][1] % mod * g[i][1] % mod + mod ) % mod;
printf( "%lld\n", ans );
return 0;
}
B - Insurance
扑面而来的三分气息
看题目描述不可能是二分,看看式子,更加觉得三分有理
#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 100005
#define eps 1e-7
int n;
double A[maxn];
double check( double x ) {
double ans = 0;
for( int i = 1;i <= n;i ++ )
ans += x + A[i] - min( A[i], x * 2 );
return ans / n;
}
int main() {
scanf( "%d", &n );
for( int i = 1;i <= n;i ++ )
scanf( "%lf", &A[i] );
double l = 0, r = 1e9;
while( r - l > eps ) {
double mid1 = l + ( r - l ) / 3, mid2 = r - ( r - l ) / 3;
if( check( mid1 ) > check( mid2 ) ) l = mid1;
else r = mid2;
}
printf( "%.10f\n", check( l ) );
return 0;
}
C - Calculator
果然是套斐波拉契,看操作就能发现端倪,更全面的应该是斐波拉契套倍增
#include <stack>
#include <cstdio>
#include <vector>
using namespace std;
#define int long long
stack < int > st;
vector < int > ans;
int fib[100];
int n;
signed main() {
scanf( "%lld", &n );
fib[1] = fib[2] = 1;
for( int i = 3;i <= 88;i ++ )
fib[i] = fib[i - 1] + fib[i - 2];
for( int i = 88;i;i -- )
if( n >= fib[i] ) st.push( i ), n -= fib[i];
int ip = 0;
while( ! st.empty() ) {
int x = st.top(); st.pop();
if( x & 1 ) {
while( ip < x - 1 ) {
if( ip & 1 ) ans.push_back( 4 );
else ans.push_back( 3 );
ip ++;
}
ans.push_back( 1 );
}
else {
while( ip < x - 1 ) {
if( ip & 1 ) ans.push_back( 4 );
else ans.push_back( 3 );
ip ++;
}
ans.push_back( 2 );
}
}
printf( "%lld\n", ans.size() );
for( int i = ans.size() - 1;~ i;i -- )
printf( "%lld\n", ans[i] );
}
D - XOR Game
显然是跟二进制挂钩
想最后异或的答案最小,换言之贪心二进制从高位到低位顺次考虑尽可能使高位为 0 0 0
那么就需要 0 − 0 , 1 − 1 0-0,1-1 0−0,1−1,除非迫不得已再 0 − 1 0-1 0−1从而导致该位产生贡献
而这迫不得已的情况就是该位为 0 0 0( 1 1 1)的数字个数为奇数时,就必定会产生一条 0 − 1 0-1 0−1的配对
否则按 0 − 0 , 1 − 1 0-0,1-1 0−0,1−1配对方式将数划分成两堆各自重复上述过程
显然这个应该放在字典树上去搞,套着递归求解
#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 200005
int cnt = 1, n, ret;
int siz[maxn * 30], trie[maxn * 30][2];
void insert( int x ) {
int now = 1;
siz[now] ++;
for( int i = 29;~ i;i -- ) {
int k = x >> i & 1;
if( ! trie[now][k] ) trie[now][k] = ++ cnt;
now = trie[now][k];
siz[now] ++;
}
}
int work( int x1, int x2, int d ) {
int ans = 1 << 30;
if( ! x1 ) return ans;
if( ! d ) return 0;
for( int i = 0;i < 2;i ++ )
if( trie[x2][i] )
ans = min( ans, work( trie[x1][i], trie[x2][i], d - 1 ) );
else
ans = min( ans, work( trie[x1][i], trie[x2][i ^ 1], d - 1 ) + ( 1 << d - 1 ) );
return ans;
}
void solve( int x, int d ) {
if( ! x ) return;
if( siz[trie[x][0]] & 1 )
ret = max( ret, work( trie[x][0], trie[x][1], d - 1 ) + ( 1 << d - 1 ) );
else {
solve( trie[x][0], d - 1 );
solve( trie[x][1], d - 1 );
}
}
int main() {
scanf( "%d", &n );
for( int i = 1, x;i <= ( n << 1 );i ++ ) {
scanf( "%d", &x );
insert( x );
}
solve( 1, 30 );
printf( "%d\n", ret );
return 0;
}
E - Increasing LCMs
要想 l c m lcm lcm单增,那么每次的最后数字 A i A_i Ai一定是非常特殊的,它满足拥有一个 [ 1 , i ) [1,i) [1,i)都没有的质因子的幂
贪心的有每次选择这样特殊的数字扔到最后,显然扔的数字越多,剩下的数成为特殊数字的可能性越大
直接暴力模拟选取
#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
#define maxn 105
int n;
int A[maxn], id[maxn];
int d[maxn][maxn];
int gcd( int x, int y ) {
if( x < y ) swap( x, y );
if( ! y ) return x;
else return gcd( y, x % y );
}
int lcm( int x, int y ) {
return x / gcd( x, y ) * y;
}
signed main() {
scanf( "%lld", &n );
for( int i = 1;i <= n;i ++ )
scanf( "%lld", &A[i] ), id[i] = i;
for( int i = 1;i <= n;i ++ )
for( int j = i + 1;j <= n;j ++ )
d[i][j] = d[j][i] = gcd( A[i], A[j] );
for( int i = n;i;i -- ) {
bool flag = 0;
for( int j = 1;j <= i;j ++ ) {
int g = 1;
for( int k = 1;k <= i;k ++ )
if( j == k ) continue;
else g = lcm( g, d[id[j]][id[k]] );
if( g == A[id[j]] ) continue;
else {
for( int k = j;k < i;k ++ )
swap( id[k], id[k + 1] );
flag = 1;
break;
}
}
if( ! flag ) return ! printf( "No\n" );
}
printf( "Yes\n" );
for( int i = 1;i <= n;i ++ )
printf( "%lld ", A[id[i]] );
return 0;
}
ABC-206
A - Maxi-Buying
翻译题面,条件判断即可
B - Savings
x ( x − 1 ) 2 ≥ n , O ( n ) \frac{x(x-1)}{2}\ge n,O(\sqrt{n}) 2x(x−1)≥n,O(n)枚举 x x x即可
C - Swappable
O ( n ) O(n) O(n)线性,边输入边做, m a p map map统计 x x x出现次数,用已统计的个数减去出现次数即可
D - KAIBUNsyo
直接扫,不对应随便修改其中一个即可,因为修改需要传递,直接并查集维护即可
E - Divide Both
做过一道非常相似的莫比乌斯反演,求 g c d ( x , y ) = 1 , a ≤ x ≤ b , c ≤ y ≤ d gcd(x,y)=1,a\le x\le b,c\le y\le d gcd(x,y)=1,a≤x≤b,c≤y≤d的数对个数
拆分成 S ( b , d ) − S ( a , d ) − S ( b , c ) + S ( a , c ) S(b,d)-S(a,d)-S(b,c)+S(a,c) S(b,d)−S(a,d)−S(b,c)+S(a,c)
其中 S ( n , m ) : g c d ( x , y ) = 1 , 1 ≤ x ≤ n , 1 ≤ y ≤ m S(n,m):gcd(x,y)=1,1\le x\le n,1\le y\le m S(n,m):gcd(x,y)=1,1≤x≤n,1≤y≤m的数对个数
对于此题,先发制人枚举 g c d gcd gcd,转化为求 g c d ( x , y ) = 1 , ⌈ L g ⌉ ≤ x , y ≤ ⌊ R g ⌋ gcd(x,y)=1,\lceil\frac{L}{g}\rceil\le x,y\le \lfloor\frac{R}{g}\rfloor gcd(x,y)=1,⌈gL⌉≤x,y≤⌊gR⌋的数对个数
在最后枚举 i ∈ [ L , R ] i∈[L,R] i∈[L,R]减去其因子个数 c n t cnt_{} cnt和其倍数 R i \frac{R}{i} iR
#pragma GCC optimize(2)
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define int long long
#define maxn 1000005
vector < int > d[maxn];
int cnt;
bool vis[maxn];
int prime[maxn], mu[maxn], pre[maxn];
void init( int n ) {
mu[1] = 1;
for( int i = 2;i <= n;i ++ ) {
if( ! vis[i] ) prime[++ cnt] = i, mu[i] = -1;
for( int j = 1;j <= cnt && i * prime[j] <= n;j ++ ) {
vis[i * prime[j]] = 1;
if( i % prime[j] == 0 ) {
mu[i * prime[j]] = 0;
break;
}
mu[i * prime[j]] = -mu[i];
}
}
for( int i = 1;i <= n;i ++ ) pre[i] = pre[i - 1] + mu[i];
}
int calc( int n, int m ) {
if( n > m ) swap( n, m );
int last, ans = 0;
for( int i = 1;i <= n;i = last + 1 ) {
last = min( n / ( n / i ), m / ( m / i ) );
ans += ( pre[last] - pre[i - 1] ) * ( n / i ) * ( m / i );
}
return ans;
}
signed main() {
int L, R;
scanf( "%lld %lld", &L, &R );
init( R );
int ans = 0;
for( int k = 2;k <= R;k ++ ) {
int l = ( L - 1 ) / k, r = R / k;
ans += calc( r, r ) - calc( l, r ) - calc( r, l ) + calc( l, l );
}
for( int i = 2;i <= R;i ++ )
for( int j = i << 1;j <= R;j += i )
d[j].push_back( i );
for( int i = max( 2ll, L );i <= R;i ++ ) {//1压根没有被前面莫比乌斯反演算进去 所以不能枚举 枚举了反而可能会让答案少n
for( int j = d[i].size() - 1;~ j;j -- )
if( d[i][j] >= L ) ans --;
else break;
ans -= ( R / i );
}
printf( "%lld\n", ans );
return 0;
}
F - Interval Game 2
S G SG SG函数一学就是一万年,嘤嘤嘤(╥╯^╰╥)
直接送个链接
#include <map>
#include <cstdio>
#include <cstring>
using namespace std;
#define maxn 105
int T, n;
int L[maxn], R[maxn];
int dp[maxn][maxn];
int dfs( int l, int r ) {
if( l >= r ) return 0;
if( ~ dp[l][r] ) return dp[l][r];
map < int, bool > vis;
for( int i = 1;i <= n;i ++ )
if( l <= L[i] && R[i] <= r )
vis[dfs( l, L[i] ) ^ dfs( R[i], r )] = 1;
for( int i = 0;;i ++ )
if( ! vis[i] ) return dp[l][r] = i;
}
int main() {
scanf( "%d", &T );
while( T -- ) {
scanf( "%d", &n );
for( int i = 1;i <= n;i ++ )
scanf( "%d %d", &L[i], &R[i] );
memset( dp, -1, sizeof( dp ) );
if( dfs( 1, 100 ) ) printf( "Alice\n" );
else printf( "Bob\n" );
}
return 0;
}
ABC-209
C - Not Equal
对于 i , i + 1 i,i+1 i,i+1如果 i + 1 i+1 i+1的上限比 i i i大,那么 i i i无论怎么选, i + 1 i+1 i+1的选择都是 a i + 1 − 1 a_{i+1}-1 ai+1−1
从这里得到性质,若 [ 1 , i ] [1,i] [1,i]是不下降序列,则 i i i的选择为 a i − ( i − 1 ) a_i-(i-1) ai−(i−1),求乘积即可
恰好此题只需要排个序就能满足上述性质
#include <cstdio>
#include <algorithm>
using namespace std;
#define mod 1000000007
#define int long long
#define maxn 200005
int n, ans;
int c[maxn];
signed main() {
scanf( "%lld", &n );
for( int i = 1;i <= n;i ++ )
scanf( "%lld", &c[i] );
sort( c + 1, c + n + 1 );
ans = 1;
for( int i = 1;i <= n;i ++ )
ans = ans * ( c[i] - i + 1 ) % mod;
printf( "%lld\n", ans );
return 0;
}
D - Collision
树,边权相同,相同速度从两个点朝对方移动
完全就是树上 l c a lca lca标配,直接求两个点之间距离的奇偶就知道是在路上还是点上
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define maxn 200005
vector < int > G[maxn];
int n, Q;
int dep[maxn];
int f[maxn][20];
void dfs( int u, int fa ) {
dep[u] = dep[fa] + 1, f[u][0] = fa;
for( int i = 1;i < 20;i ++ )
f[u][i] = f[f[u][i - 1]][i - 1];
for( int i = 0;i < G[u].size();i ++ ) {
int v = G[u][i];
if( v == fa ) continue;
else dfs( v, u );
}
}
int GetLca( int u, int v ) {
if( dep[u] < dep[v] ) swap( u, v );
for( int i = 19;~ i;i -- )
if( dep[f[u][i]] >= dep[v] )
u = f[u][i];
if( u == v ) return u;
for( int i = 19;~ i;i -- )
if( f[u][i] != f[v][i] )
u = f[u][i], v = f[v][i];
return f[u][0];
}
int main() {
int n, Q, u, v;
scanf( "%d %d", &n, &Q );
for( int i = 1;i < n;i ++ ) {
scanf( "%d %d", &u, &v );
G[u].push_back( v );
G[v].push_back( u );
}
dfs( 1, 0 );
while( Q -- ) {
scanf( "%d %d", &u, &v );
int lca = GetLca( u, v );
if( ( dep[u] + dep[v] - ( dep[lca] << 1 ) ) & 1 )
printf( "Road\n" );
else
printf( "Town\n" );
}
return 0;
}
E - Shiritori
是个标准的图上博弈问题。
将一个单词看成图上的一条边,首三个字母向末三个字母连一条有向边(哈希或者map
编号)
-
对于没有出边的顶点,状态为
N
(必胜态)——初始化局面 -
对于一个没有确定态的顶点
- 如果有一条出边指向
P
(必败态),则该顶点为N
- 如果所有出边都是
N
,则该顶点为P
- 如果有一条出边指向
用类拓扑方法,确定状态点才会被放进队列,最后若还是未知态则是Draw
局面
发现,初始局面确定状态的点进行转移,需要知道其前驱(指向他的多个点)
因此图上博弈问题建的是反图,初始局面的已知态点变成了反图中没有入度的点
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
#define maxn 200005
queue < int > q;
pair < int, int > edge[maxn];
vector < int > G[maxn];
int n;
char s[10];
int ans[maxn], out_d[maxn];
int num( char x ) {
if( x >= 'A' && x <= 'Z' ) return x - 'A';
else return x - 'a' + 26;
}
int Hash( char i, char j, char k ) {
return num( k ) + 52 * num( j ) + 52 * 52 * num( i );
}
int main() {
scanf( "%d", &n );
for( int i = 1;i <= n;i ++ ) {
scanf( "%s", s + 1 );
int len = strlen( s + 1 );
edge[i] = make_pair( Hash( s[1], s[2], s[3] ), Hash( s[len - 2], s[len - 1], s[len] ) );
out_d[edge[i].first] ++;
G[edge[i].second].push_back( edge[i].first );
}
for( int i = 0;i < 53 * 53 * 53;i ++ )
if( ! out_d[i] ) {
q.push( i );
ans[i] = 1;
}
while( ! q.empty() ) {
int u = q.front(); q.pop();
for( int i = 0;i < G[u].size();i ++ ) {
int v = G[u][i];
if( ! ans[v] ) {
-- out_d[v];
if( ans[u] == 1 ) ans[v] = -1, q.push( v );
else if( ! out_d[v] ) ans[v] = 1, q.push( v );
}
}
}
for( int i = 1;i <= n;i ++ )
switch( ans[edge[i].second] ) {
case 1 : {
printf( "Takahashi\n" );
break;
}
case -1 : {
printf( "Aoki\n" );
break;
}
case 0 : {
printf( "Draw\n" );
break;
}
}
return 0;
}
F - Deforestation
考虑相邻两棵树 i − 1 , i i-1,i i−1,i,如果先砍 i − 1 i-1 i−1再砍 i i i,贡献为 h i − 1 + h i + h i h_{i-1}+h_i+h_i hi−1+hi+hi;如果先砍 i i i再砍 i − 1 i-1 i−1,贡献为 h i + h i − 1 + h i − 1 h_i+h_{i-1}+h_{i-1} hi+hi−1+hi−1
显然,后砍的树贡献次数要多一次
所以我们贪心地得出先砍较高的树
设 d p i , j : dp_{i,j}: dpi,j:所有排列中使得 i i i树是第 j j j个被砍掉的数量(插头 d p dp dp)
-
h i = h i − 1 h_i=h_{i-1} hi=hi−1,则无所谓先后
d p i , j = ∑ k = 1 i − 1 d p i − 1 , k dp_{i,j}=\sum_{k=1}^{i-1}dp_{i-1,k} dpi,j=∑k=1i−1dpi−1,k
-
h i > h i − 1 h_i>h_{i-1} hi>hi−1
d p i , j = ∑ k = j i − 1 d p i − 1 , k dp_{i,j}=\sum_{k=j}^{i-1}dp_{i-1,k} dpi,j=∑k=ji−1dpi−1,k
-
h i < h i − 1 h_i<h_{i-1} hi<hi−1
d p i , j = ∑ k = 1 j − 1 d p i − 1 , k dp_{i,j}=\sum_{k=1}^{j-1}dp_{i-1,k} dpi,j=∑k=1j−1dpi−1,k
#include <cstdio>
#define maxn 4005
#define int long long
#define mod 1000000007
int n;
int h[maxn];
int dp[maxn][maxn];
signed main() {
scanf( "%lld", &n );
for( int i = 1;i <= n;i ++ )
scanf( "%lld", &h[i] );
h[0] = h[1], dp[0][0] = 1;
for( int i = 1;i <= n;i ++ ) {
for( int j = 1;j <= i;j ++ ) {
if( h[i] == h[i - 1] )
dp[i][j] = dp[i - 1][i - 1];
else if( h[i] > h[i - 1] )
dp[i][j] = ( dp[i - 1][i - 1] - dp[i - 1][j - 1] + mod ) % mod;
else
dp[i][j] = dp[i - 1][j - 1];
}
for( int j = 1;j <= i;j ++ )
dp[i][j] = ( dp[i][j] + dp[i][j - 1] ) % mod;
}
printf( "%lld\n", dp[n][n] );//已经是dp[n][1~n]的前缀和
return 0;
}
luogu
LGR-87(Div.2)
远古档案馆
2x2
不无脑爆搜才是没有脑子
#include <cstdio>
#include <iostream>
#include <map>
using namespace std;
int s[3][3], t[3][3];
map < int, int > mp;
int End;
int id( int g[3][3] ) {
int num = 0;
for( int i = 1;i <= 2;i ++ )
for( int j = 1;j <= 2;j ++ )
num = num * 10 + g[i][j];
return num;
}
void dfs( int g[3][3] ) {
int now = id( g );
if( now == End ) {
printf( "Yes\n" );
exit( 0 );
}
if( mp[now] ) return;
mp[now] = 1;
int tmp[3][3] = {};
for( int i = 1;i <= 2;i ++ )
for( int j = 1;j <= 2;j ++ )
tmp[i][j] = g[i][j];
for( int i = 1;i <= 2;i ++ )
for( int j = 1;j <= 2;j ++ )
if( ! tmp[i][j] ) continue;
else {
if( i > 1 && ! tmp[i - 1][j] ) {
swap( tmp[i - 1][j], tmp[i][j] );
dfs( tmp );
for( int x = 1;x <= 2;x ++ )
for( int y = 1;y <= 2;y ++ )
tmp[x][y] = g[x][y];
}
if( j > 1 && ! tmp[i][j - 1] ) {
swap( tmp[i][j], tmp[i][j - 1] );
dfs( tmp );
for( int x = 1;x <= 2;x ++ )
for( int y = 1;y <= 2;y ++ )
tmp[x][y] = g[x][y];
}
if( i < 2 && ! tmp[i + 1][j] ) {
swap( tmp[i][j], tmp[i + 1][j] );
dfs( tmp );
for( int x = 1;x <= 2;x ++ )
for( int y = 1;y <= 2;y ++ )
tmp[x][y] = g[x][y];
}
if( j < 2 && ! tmp[i][j + 1] ) {
swap( tmp[i][j], tmp[i][j + 1] );
dfs( tmp );
for( int x = 1;x <= 2;x ++ )
for( int y = 1;y <= 2;y ++ )
tmp[x][y] = g[x][y];
}
}
}
int main() {
for( int i = 1;i <= 2;i ++ )
for( int j = 1;j <= 2;j ++ )
scanf( "%d", &s[i][j] );
for( int i = 1;i <= 2;i ++ )
for( int j = 1;j <= 2;j ++ )
scanf( "%d", &t[i][j] );
End = id( t );
dfs( s );
printf( "No\n" );
return 0;
}
珍珠帝王蟹
-
暴力大讨论多种情况,ε=ε=ε=┏(゜ロ゜;)┛
-
因为
1<|v|
,所以肯定是不能放过任何一个乘数的-
所有乘数乘积为正
-
负乘数个数为偶数
先把加正数的操作做了,再乘所有乘数,最后减去所有负数
-
负乘数个数为奇数
先把加正数的操作做了,再乘一个最大的负乘数,然后加上所有负整数操作,最后一起乘剩下乘数
-
-
所有乘数乘积为负
先把负数加在一起,再乘一个最大的负乘数,转化为正数,然后加上所有正数操作,最后乘所有乘数
-
#include <cstdio>
#define int long long
#define mod 998244353
int n, v, add, add_, mul = 1, t = 1;
char op[5];
signed main() {
scanf( "%lld", &n );
for( int i = 1;i <= n;i ++ ) {
scanf( "%s %lld", op, &v );
if( op[0] == '+' )
if( v > 0 ) add = ( add + v ) % mod;
else add_ = ( add_ + v ) % mod;
else
if( t > 0 || ( v > t && v < 0 ) ) mul = mul * t % mod, t = v;
else mul = mul * v % mod;
}
if( mul * t > 0 )
if( t > 0 ) printf( "%lld\n", ( add * mul % mod * t % mod + add_ + mod ) % mod );
else printf( "%lld\n", ( add * mul % mod * t % mod + add_ * mul % mod + mod ) % mod );
else
printf( "%lld\n", ( add_ * mul % mod * t % mod + add * mul % mod + mod ) % mod );
return 0;
}
天体探测仪
-
∀ i \forall i ∀i,设 [ l i , r i ] : [l_i,r_i]: [li,ri]: i i i是区间内最小值的最长区间
-
考虑在 [ l i , r i ] [l_i,r_i] [li,ri]中如何确定 i i i的位置,令 l e n = r i − l i + 1 len=r_i-l_i+1 len=ri−li+1
- 对于长度 = j = j =j的区间个数为 l e n − j + 1 len-j+1 len−j+1,若所有区间都包含 i i i,那么 i i i应该出现 l e n − j + 1 len-j+1 len−j+1次
- 找到最大的 j j j,使得 i i i出现次数 < l e n − j + 1 <len-j+1 <len−j+1,此时 i i i的位置可以为 l i + j / r i − j l_i+j/r_i-j li+j/ri−j
-
在操作过程中,显然只用到了区间的长度 l e n len len,并不关心区间真正是什么样子
-
巧合的,发现 l e n len len在输入中就已经给出,最大的 j j j满足 i ∈ S j → l e n = j i∈S_{j}\rightarrow len=j i∈Sj→len=j
-
用队列 q i q_i qi维护当前长度为 i i i的空区间,队列内的每个元素代表一个区间的左端点
初始化 q n q_n qn存了 [ 1 , n ] [1,n] [1,n]整个序列,然后从 1 1 1到 n n n开始放数
每次取出 q l e n q_{len} qlen的队头,将区间裂成 l e n ′ = j , l e n ′ ′ = l e n − j − 1 len'=j,len''=len-j-1 len′=j,len′′=len−j−1
#include <queue>
#include <cstdio>
using namespace std;
#define maxn 805
queue < int > q[maxn];
int n;
int last[maxn], ans[maxn];
int cnt[maxn][maxn];
int main() {
scanf( "%d", &n );
for( int i = 1;i <= n;i ++ )
for( int j = 1, k;j <= n - i + 1;j ++ ) {
scanf( "%d", &k );
cnt[k][i] ++, last[k] = i;
}
q[n].push( 1 );
for( int i = 1;i <= n;i ++ ) {
int pos = 0;
for( int j = last[i] - 1;j;j -- )
if( cnt[i][j] < cnt[i][last[i]] + last[i] - j ) {
pos = j;
break;
}
int l = q[last[i]].front();//last[i]就是len(i)
q[last[i]].pop();
ans[l + pos] = i;
q[pos].push( l );
q[last[i] - pos - 1].push( l + pos + 1 );//放的是新区间的左端点L
}
for( int i = 1;i <= n;i ++ )
printf( "%d ", ans[i] );
return 0;
}