_Alexande_ Round 2 题解
「盛夏!海岛?大冒险!」
其实听过我那天讲课的人都知道这个题有一个很典的做法。
就是每次扫,然后扫到一个数把前面到第 \(x\) 与这个元素相同的 \(x\) 的 \(d\) 数组贡献设为 \(1\),然后前面第 \(x + 1\) 个元素把贡献设为 \(-1\),前面第 \(x + 2\) 个元素贡献更改为 \(0\),这个东西明显可以用线段树维护。
然后求得话就是问你所有以 \(i\) 为后缀的和的和,这个东西单点修改用线段树摊掉,然后维护一个整体查询就好了。
时间复杂度 \(O(n \log_2 n)\),空间复杂度 \(O(n)\)。
Code:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define fir first
#define sec second
#define mkp make_pair
#define pb push_back
#define lep( i, l, r ) for ( int i = ( l ); i <= ( r ); ++ i )
#define rep( i, r, l ) for ( int i = ( r ); i >= ( l ); -- i )
typedef unsigned long long ull;
typedef long long ll;
typedef long double ld;
typedef pair < int, int > pii;
char _c; bool _f; template < class type > inline void read ( type &x ) {
_f = 0, x = 0;
while ( _c = getchar (), !isdigit ( _c ) ) if ( _c == '-' ) _f = 1;
while ( isdigit ( _c ) ) x = x * 10 + _c - '0', _c = getchar (); if ( _f ) { x = -x; }
}
template < class type > inline void chkmin ( type &x, type y ) { x = ( x <= y ? x : y ); }
template < class type > inline void chkmax ( type &x, type y ) { x = ( x >= y ? x : y ); }
const int N = 1e6 + 5;
int n, m;
int a[N], pre[N], nxt[N], f[N], g[N], tong[N], p[N];
int tree[N << 2], tag[N << 2];
void pushup ( int node ) {
tree[node] = tree[node << 1] + tree[node << 1 | 1];
}
void addtag ( int node, int lt, int rt, int k ) {
tree[node] += ( rt - lt + 1 ) * k;
tag[node] += k;
}
void pushdown ( int node, int lt, int rt ) {
int mid = lt + rt >> 1;
addtag ( node << 1, lt, mid, tag[node] ), addtag ( node << 1 | 1, mid + 1, rt, tag[node] );
tag[node] = 0;
}
void update ( int node, int lt, int rt, int x, int y, int k ) {
if ( y < lt || x > rt ) {
return ;
}
if ( x <= lt && rt <= y ) {
addtag ( node, lt, rt, k );
return ;
}
pushdown ( node, lt, rt );
int mid = lt + rt >> 1;
update ( node << 1, lt, mid, x, y, k ), update ( node << 1 | 1, mid + 1, rt, x, y, k );
pushup ( node );
}
int query ( int node, int lt, int rt, int x, int y ) {
if ( y < lt || x > rt ) {
return 0;
}
if ( x <= lt && rt <= y ) {
return tree[node];
}
pushdown ( node, lt, rt );
int mid = lt + rt >> 1;
return query ( node << 1, lt, mid, x, y ) + query ( node << 1 | 1, mid + 1, rt, x, y );
}
void Solve () {
ios :: sync_with_stdio ( false );
cin.tie ( 0 ), cout.tie ( 0 );
cin >> n;
for ( int i = 1; i <= n; i ++ ) {
cin >> a[i];
}
for ( int i = 1; i <= n; i ++ ) {
if ( a[i] > n ) {
continue;
}
pre[i] = f[a[i]];
nxt[f[a[i]]] = i;
f[a[i]] = i;
}
for ( int i = n; i >= 1; i -- ) {
if ( a[i] > n ) {
continue;
}
g[a[i]] = i;
}
int ans = 0;
for ( int i = 1; i <= n; i ++ ) {
if ( a[i] > n ) {
continue;
}
tong[a[i]] ++;
if ( tong[a[i]] == a[i] ) {
p[a[i]] = g[a[i]];
}
else if ( tong[a[i]] > a[i] ) {
p[a[i]] = nxt[p[a[i]]];
}
if ( p[a[i]] ) {
update ( 1, 1, n, 1, p[a[i]], 1 );
}
if ( pre[p[a[i]]] ) {
update ( 1, 1, n, 1, pre[p[a[i]]], -2 );
}
if ( pre[pre[p[a[i]]]] ) {
update ( 1, 1, n, 1, pre[pre[p[a[i]]]], 1 );
}
ans += query ( 1, 1, n, 1, i );
// for ( int j = 1; j <= n; j ++ ) {
// cout << query ( 1, 1, n, i, i ) << " ";
// }
// cout << '\n';
}
cout << ans;
}
signed main () {
#ifdef judge
freopen ( "Code.in", "r", stdin );
freopen ( "Code.out", "w", stdout );
freopen ( "Code.err", "w", stderr );
#endif
Solve ();
return 0;
}
「仲夏!幻夜?奇想曲!」
我们考虑异或然后平方没有啥性质,所以我们拆位,算出每一位的 \(0/1\) 期望,然后这个异或的平方就被你转化成了和的平方(每一位期望的和的平方),但是你发现如果你拆位处理那么问题就变得独立了,这是很不好的。
但是根据期望的线性性,我们可以把平方用二项式展开的方式给摊掉,此时发现贡献只与 \(i, j\) 两项有关,本质上就是直接可以允许其不独立,所以你 \((\log V)^2\) 暴力强行对于每对 \(i, j\) 都 DP 一下就好了(相当于是 \(a_i \le 3\))时的 DP 做法。
具体来说就是只要求每对 \(i, j\) 的期望就可以避免独立性的问题了。
可能具体实现的时候那里 \((\log V)^2\) 需要折半一下减小常数才能过。
Code:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define fir first
#define sec second
#define mkp make_pair
#define pb push_back
#define lep( i, l, r ) for ( int i = ( l ); i <= ( r ); ++ i )
#define rep( i, r, l ) for ( int i = ( r ); i >= ( l ); -- i )
typedef long long ll;
typedef long double ld;
typedef pair < int, int > pii;
char _c; bool _f; template < class type > inline void read ( type &x ) {
_f = 0, x = 0;
while ( _c = getchar (), !isdigit ( _c ) ) if ( _c == '-' ) _f = 1;
while ( isdigit ( _c ) ) x = x * 10 + _c - '0', _c = getchar (); if ( _f ) { x = -x; }
}
template < class type > inline void chkmin ( type &x, type y ) { x = ( x <= y ? x : y ); }
template < class type > inline void chkmax ( type &x, type y ) { x = ( x >= y ? x : y ); }
const int N = 1e5 + 5;
const int mod = 1e9 + 7;
int n;
int a[N], p[N], q[N], f[N][4], b[N], ans;
int fast_pow ( int a, int b ) {
int res = 1;
while ( b ) {
if ( b & 1 ) {
res = res * a;
res %= mod;
}
b >>= 1;
a = a * a;
a %= mod;
}
return res;
}
void Solve () {
ios :: sync_with_stdio ( false );
cin.tie ( 0 ), cout.tie ( 0 );
cin >> n;
for ( int i = 1; i <= n; i ++ ) {
cin >> a[i];
}
for ( int i = 1; i <= n; i ++ ) {
cin >> p[i];
q[i] = ( 1000000000 - p[i] ) * fast_pow ( 1000000000, mod - 2 ) % mod;
p[i] = p[i] * fast_pow ( 1000000000, mod - 2 ) % mod;
}
for ( int i = 0; i < 30; i ++ ) {
for ( int j = i; j < 30; j ++ ) {
for ( int k = 1; k <= n; k ++ ) {
b[k] = ( ( a[k] >> i ) & 1ll ) + 2ll * ( ( a[k] >> j ) & 1ll );
}
for ( int k = 1; k <= n; k ++ ) {
for ( int l = 0; l <= 3; l ++ ) {
f[k][l] = 0;
}
}
f[0][0] = 1;
for ( int k = 1; k <= n; k ++ ) {
for ( int l = 0; l <= 3; l ++ ) {
f[k][l] = p[k] * f[k - 1][l ^ b[k]] % mod + q[k] * f[k - 1][l] % mod;
f[k][l] %= mod;
}
}
ans += f[n][3] * fast_pow ( 2, i + j ) % mod * ( i != j ? 2 : 1 ) % mod;
ans %= mod;
}
}
cout << ans;
}
signed main () {
#ifdef judge
freopen ( "Code.in", "r", stdin );
freopen ( "Code.out", "w", stdout );
freopen ( "Code.err", "w", stderr );
#endif
Solve ();
return 0;
}
「清夏!乐园?大秘境!」
这个题是 AT 的一道原题,感觉题目很神。
就是你发现直接贪心很明显是有问题的,你考虑这样一个贪心过程:
- 很明显从后往前贪心,当我们遇到一个 O,我们寻找我们有没有待匹配的 I,如果我们遇到一个 N,我们寻找我们有没有待匹配的 OI。
但是当我们遇到了 I,我们就不知道咋办了,我们不知道它是作为一个 IOI 的开头,还是 NOI 和 IOI 的结尾。
然后我们发现答案很明显是具有单调性的,就是答案越大,就会有越多的 I 满足是 IOI 和 NOI 的结尾,
所以当我们二分一个答案 \(mid\) 且遇到一个 I 时,我们判断现有的 OI 和 I 的量有没有到达 \(mid\),如果没有达到,我们肯定是新开一个更好,因为你到后面可能就开不了了,否则就直接配前面有的。
最后 Check 的时候看有没有 \(mid\) 对就完了。
#include <bits/stdc++.h>
using namespace std;
// #define int long long
#define fir first
#define sec second
#define mkp make_pair
#define pb push_back
#define lep( i, l, r ) for ( int i = ( l ); i <= ( r ); ++ i )
#define rep( i, r, l ) for ( int i = ( r ); i >= ( l ); -- i )
typedef long long ll;
typedef long double ld;
typedef pair < int, int > pii;
char _c; bool _f; template < class type > inline void read ( type &x ) {
_f = 0, x = 0;
while ( _c = getchar (), !isdigit ( _c ) ) if ( _c == '-' ) _f = 1;
while ( isdigit ( _c ) ) x = x * 10 + _c - '0', _c = getchar (); if ( _f ) { x = -x; }
}
template < class type > inline void chkmin ( type &x, type y ) { x = ( x <= y ? x : y ); }
template < class type > inline void chkmax ( type &x, type y ) { x = ( x >= y ? x : y ); }
const int N = 2e6 + 5;
int n;
string s;
bool Check ( int mid ) {
int res1 = 0, res2 = 0, res3 = 0;
for ( int i = n; i >= 1; i -- ) {
if ( s[i] == 'N' && res2 ) {
res2 --, res3 ++;
}
else if ( s[i] == 'O' && res1 ) {
res1 --, res2 ++;
}
else if ( s[i] == 'I' ) {
if ( res1 + res2 + res3 < mid ) {
res1 ++;
}
else if ( res1 + res2 + res3 >= mid && res2 ) {
res2 --, res3 ++;
}
}
}
return res3 >= mid;
}
int t;
void Solve () {
ios :: sync_with_stdio ( false );
cin.tie ( 0 ), cout.tie ( 0 );
cin >> n;
cin >> s;
n = s.size ();
s = " " + s;
int l = -1, r = n / 3 + 1;
while ( l + 1 < r ) {
int mid = l + r >> 1;
if ( Check ( mid ) ) {
l = mid;
}
else {
r = mid;
}
}
cout << l << '\n';
}
signed main () {
#ifdef judge
freopen ( "Code.in", "r", stdin );
freopen ( "Code.out", "w", stdout );
freopen ( "Code.err", "w", stderr );
#endif
Solve ();
return 0;
}
「欢夏!邪龙?童话国!」
说实话感觉这个题最典,但是迫于扫描线的难度,也只能给个这样的分。
就是我们发现在巨大网格中我们一个沾染过的格子覆盖的范围一定是一个矩形,这个矩形的形成是因为我们可以上下左右移动,然后就是 \(n\) 个矩形的面积并了,就是一道扫描线典题了。
注意答案过大,需要开 __int128。
Code:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 5;
int n, m, r, c, q;
__int128 ans;
int X[N * 2];
struct Line{
int l, r, high;
int val;
bool operator<(const Line &x) const{
return high < x.high;
}
}line[N * 4];
struct Segment_Tree{
int l, r;
int val, len;
}tree[N * 8];
void pushup(int node){
tree[node].len = (tree[node].val ? X[tree[node].r + 1] - X[tree[node].l] : tree[node << 1].len + tree[node << 1 | 1].len);
}
void build(int node, int lt, int rt){
tree[node].l = lt;
tree[node].r = rt;
tree[node].val = tree[node].len = 0;
if(lt == rt){
return ;
}
int mid = lt + rt >> 1;
build(node << 1, lt, mid);
build(node << 1 | 1, mid + 1, rt);
}
void update(int node, int x, int y, int val){
if(X[tree[node].r + 1] <= x || y <= X[tree[node].l]){
return ;
}
if(x <= X[tree[node].l] && X[tree[node].r + 1] <= y){
tree[node].val += val;
pushup(node);
return ;
}
update(node << 1, x, y, val);
update(node << 1 | 1, x, y, val);
pushup(node);
}
void write(__int128 x) {
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
void Solve(){
ios :: sync_with_stdio ( false );
cin.tie ( 0 ), cout.tie ( 0 );
cin >> n >> m >> r >> c;
cin >> q;
for(int i = 1; i <= q; i++){
int x, y;
cin >> x >> y;
int x1 = x, y1 = y, x2 = n - ( r - x ) + 1, y2 = m - ( c - y ) + 1;
// cout << x1 << " " << y1 << " " << x2 << " " << y2 << '\n';
X[2 * i - 1] = x1;
X[2 * i] = x2;
line[2 * i - 1] = {x1, x2, y1, 1};
line[2 * i] = {x1, x2, y2, -1};
}
q *= 2;
sort(line + 1, line + 1 + q);
sort(X + 1, X + 1 + q);
int len = unique(X + 1, X + 1 + q) - X - 1;
build(1, 1, len - 1);
for(int i = 1; i < q; i++){
update(1, line[i].l, line[i].r, line[i].val);
ans += ( __int128 ) tree[1].len * ( __int128 )(line[i + 1].high - line[i].high);
}
write ( ans );
}
signed main(){
#ifdef debug
freopen("Code.in", "r", stdin);
freopen("Code.out", "w", stdout);
#endif
Solve();
return 0;
}
「芙卡!洛斯?芙宁娜!」
这个题就完全是送的了,为了让选手不至于拿到很低的分数。
就是发现我们可以这么构造凸多边形:让 \(n\) 条边变为一条直线,然后再曲那么一点点,这样子答案就是 \(\sum a_i - 1\) 了qwq。
Code:
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, sum;
signed main () {
ios :: sync_with_stdio ( false );
cin >> n;
for ( int i = 1; i <= n; i ++ ) {
int x;
cin >> x;
sum += x;
}
cout << sum - 1;
return 0;
}

浙公网安备 33010602011771号