T1.Cirno的冰霜符文
难度:PJ-
这题是我在做题目P3722 [AH2017/HNOI2017]影魔
时候想出的Idea,感觉可以拿来凑一道签到题,作用是人口普查,
本来数据是2e6的,但是洛谷坑爹的20M限制,所以我只能把数据造到5e5.
然后这题很简单。
第二问直接左右扫一下即可。
第一问复杂一点,每次往左右暴力跳一格,然后跳到当前覆蓋点的记录的答案位置,可以证明每个点平均最多到达两次,然后就是严格O(n)的了。
STD:
#include<bits/stdc++.h>
using namespace std;
int n;
int h[2000005];
int pre [2000005][2];
int down[2000005][2];
void query(int now,bool flag){
int ans = pre[now][flag];
while( ans > 0 and ans <= n and h[ans] <= h[now] )ans = pre[ans][flag];
if( ans == 0 or ans == n + 1 )ans = -1;
pre[now][flag] = ans;
}
int main(){
std::ios::sync_with_stdio(false);
cin >> n;
for(int i = 1;i <= n;i ++)cin >> h[i];h[0] = h[n + 1] = 1e9;
for(int i = 1;i <= n;i ++)pre[i][0] = i - 1,pre[i][1] = i + 1;
for(int i = 1;i <= n;i ++)query( i,0 );
for(int i = n;i >= 1;i --)query( i,1 );
for(int i = 1;i <= n;i ++)down[i][0] = down[i][1] = i;
for(int i = 1;i <= n;i ++)if( h[i] > h[i - 1] )down[i][0] = down[i - 1][0];
for(int i = n;i >= 1;i --)if( h[i] > h[i + 1] )down[i][1] = down[i + 1][0];
for(int i = 1;i <= n;i ++)printf("%d %d %d %d\n",pre[i][0],pre[i][1],down[i][0],down[i][1]);
return 0;
}
T2 Cirno的数学课
难度: PJ
可以一眼秒掉的数学题,虽然我当时推了30min的式子。
不难看出某种规律 3i∗(i+1)n∗(n+1)∗(n+2) 3 i ∗ ( i + 1 ) n ∗ ( n + 1 ) ∗ ( n + 2 ) 是有隐含含义的。
先来复习一下高中数学的数列中的几个重要式子
∑ni ∑ n i = 1+2+3+…+n = n∗(n+1)2 n ∗ ( n + 1 ) 2
∑ni2 ∑ n i 2 =1+4+9+…+ n2 n 2 = n(n+1)(2n+1)6 n ( n + 1 ) ( 2 n + 1 ) 6
∑ni3 ∑ n i 3 =1+8+27+…+ n3 n 3 = n2(n+1)24 n 2 ( n + 1 ) 2 4
所以我们可以知道原数列 an=n(n+1)2 a n = n ( n + 1 ) 2 ,n项和为
Sn=∑n2+n2=(n∗(n+1)∗(2n+1)6+n∗(n+1)2)/2=n∗(n+1)(n+2)6 S n = ∑ n 2 + n 2 = ( n ∗ ( n + 1 ) ∗ ( 2 n + 1 ) 6 + n ∗ ( n + 1 ) 2 ) / 2 = n ∗ ( n + 1 ) ( n + 2 ) 6
又 ai=i∗(i+1)2 a i = i ∗ ( i + 1 ) 2
所以我们可以看出那个神奇的式子:
3i∗(i+1)n∗(n+1)∗(n+2)=aiSn 3 i ∗ ( i + 1 ) n ∗ ( n + 1 ) ∗ ( n + 2 ) = a i S n
所以数列中某个数字被选的概率是该数字除以该数列总和。
然后转化以下模型,可以理解为函数 f(x)=x∗(x+1)2 f ( x ) = x ∗ ( x + 1 ) 2 下的整数点都会被等概率的选取,然后选出的两个点(可以相同)第一个点纵座标大于第二个的概率。
然后可以很容易想到,第一个比第二个点大的概率的于第二个点比第一个点大的概率。所以只需要求出两点纵座标相等的概率即可。
然后考虑枚举每个横座标,发现都比前一个横座标多出来一些新的纵座标,具体地,对于第i个横座标,比前一个多出i个纵座标,然后后面(n-i)个数字都含有这个纵座标,然后直接发现相等方案数是 i∗(n−i+1)2 i ∗ ( n − i + 1 ) 2 求和,总方案数是 S2n S n 2 ,然后就可作(做)了。
把一式展开,用代入前面的式子,然后用总方案数减去,然后除以2就大功告成了。
由于过程过于繁琐,这里就不再展开,最终的运算结果为:
12(1−3n(n+2)) 1 2 ( 1 − 3 n ( n + 2 ) )
然后就用费马小定理或者exgcd求一下逆元就轻松A了.
STD:
#include<bits/stdc++.h>
using namespace std;
const long long mod = 998244353;
long long f_pow(long long A,long long B){
long long ans = 1;
while( B ){
if( B & 1 )( ans *= A ) %= mod;
( A *= A ) %= mod;
B >>= 1;
}
return ans;
}
long long ns(long long A){
return f_pow( A, mod - 2);
}
int main(){
int n;cin >> n;
if( n == 0 )return cout << ns(2),0;
cout << ( ns(2) * ( 1 - 3ll * ns(n) % mod * ns(n + 2) % mod ) % mod + mod ) % mod;
return 0;
}
P.S. RedBag大佬当时一眼秒了%%%.
T3 Cirno的湖畔小屋
难度: TG
这是一道很随意的水题,是出题人闲着没事的时候想到的题目qwq,感觉很水然后就出出来了。
数据很大,不难想到离散化,区间赋值加查询,不难想到线段树,然后就很可做了。
(如果你是大佬就不用看后面的了).
由于修改都是从(0,0)开始的,所以不难想到没有覆蓋的轮廓是一个单调壳,每次新的覆蓋直接先找到最前面小于y的值的位置,然后区间查询总和,区间赋值,就可以轻松A掉了。其实还是很好想的,代码难度>思维难度,所以评级为TG吧。
#include<bits/stdc++.h>
using namespace std;
namespace segment{
#define Lson ( now << 1 )
#define Rson ( now << 1 | 1 )
#define Ason ( p <= mid ? Lson : Rson )
#define mid ( ( l[now] + r[now] ) >> 1 )
#define Lrange Lson,L,min( mid,R )
#define Rrange Rson,max( mid + 1,L),R
#define Range(x) ( r[x] - l[x] + 1 )
int line[100005];
int l[400005];
int r[400005];
long long v[400005];
int mi[400005];
int lazy[400005];
void build(int now,int L,int R){
l[now] = L;r[now] = R;
if( L == R )return ;
build( Lson,L,mid );
build( Rson,mid + 1,R );
}
void push_up(int now){
v[now] = v[Lson] + v[Rson];
mi[now] = min( mi[Lson],mi[Rson] );
}
void push_down(int now){
int lz = lazy[now] ;lazy[now] = 0;
if( !lz )return ;
lazy[Lson] = lazy[Rson] = lz;
mi[Lson] = mi[Rson] = lz;
v[Lson] = 1ll * ( line[ r[Lson] ] - line[ l[Lson] - 1 ] ) * lz ;
v[Rson] = 1ll * ( line[ r[Rson] ] - line[ l[Rson] - 1 ] ) * lz ;
}
int query(int now,int val){
if( l[now] == r[now] )return l[now];
push_down(now);
return query( mi[Lson] <= val ? Lson : Rson, val );
}
long long modify(int now,int L,int R,int val){
if( l[now] == L and r[now] == R ){
long long ans = 1ll * ( line[ r[now] ] - line[ l[now] - 1 ] ) * val - v[now];
v[now] += ans;
mi[now] = val;
lazy[now] = val;
return ans;
}
push_down(now);
long long Lans = 0,Rans = 0;
if( L <= mid )Lans = modify( Lrange,val );
if( R > mid )Rans = modify( Rrange,val );
push_up(now);
return Lans + Rans;
}
}
vector<int>que;
int line[100005][2];
int main(){
int n;cin >> n;que.push_back(0);
for(int i = 1;i <= n;i ++){
cin >> line[i][0] >> line[i][1];
que.push_back( line[i][0] );
}
sort( que.begin(),que.end() );
auto Uend = unique( que.begin(),que.end() );
int m = Uend - que.begin();
segment::build( 1,1,m );
for(int i = 1;i <= m;i ++ ){
segment::line[i] = que[i];
}
for(int i = 1;i <= n;i ++ ){
int x = lower_bound( que.begin(),Uend,line[i][0] ) - que.begin() ;
int y = segment::query( 1,line[i][1] ) ;
if( y > x ){ cout << "0\n"; continue; }
cout << segment::modify( 1,y,x,line[i][1] ) << "\n";
}
return 0;
}
T4 Cirno的冰霜剑
难度:TG
似乎很水,这是出题人反复迭代过的一道题,原本是一个不可做的题目,然后就改成了这样,因为这个难度出题人终于能做出来了。由于出题是一个先有题目后又STD的过程,所以我也没有想到会转化成水题的模型,其实就是一个普普通通的费用流。
前面的预处理直接暴力是 O(n3) O ( n 3 ) 的,加点优化可以变成 O(n2lnn) O ( n 2 l n n ) 但这不是主要复杂度,本来出题人想在这个点上出题的,但是太水了。学过费用流的都知道接下来是水的。所以关于建模我就不再赘述了qwq.
STD:
#include<bits/stdc++.h>
using namespace std;
namespace SPFA{
vector<int>to [10005];
vector<int>flow[10005];
vector<int>cost[10005];
vector<int>pair[10005];
int Flow,Cost;
int F,T;
void init(int _f,int _t){
F = _f;T = _t;
}
void add(int u,int v,int w,int c){
pair[u].push_back( to[v].size() );
pair[v].push_back( to[u].size() );
to[u].push_back(v);
to[v].push_back(u);
flow[u].push_back(w);
flow[v].push_back(0);
cost[u].push_back(c);
cost[v].push_back(-c);
}
bool SPFA(){
static int len[10005];memset(len,1,sizeof(len));len[F] = 0;
static int cap[10005];memset(cap,0,sizeof(cap));cap[F] = 1e9;
static int from[10005],edge[10005];
queue<int>Q;Q.push(F);
while( !Q.empty() ){
int now = Q.front();Q.pop();
for(int i = 0;i < to[now].size();i ++){
int next = to[now][i];
if( !flow[now][i] )continue;
if( len[now] + cost[now][i] >= len[next] )continue;
len[next] = len[now] + cost[now][i];
cap[next] = min( cap[now],flow[now][i] );
from[next] = now;
edge[next] = i;
Q.push(next);
}
}
if( !cap[T] )return false;
int now = T;
Flow += cap[T];
Cost += cap[T] * len[T];
while( now != F ){
flow[ from[now] ][ edge[now] ] -= cap[T];
flow[ now ][ pair[ from[now] ][ edge[now] ] ] += cap[T];
now = from[now];
}
return true;
}
void get_ans(){
while( SPFA() );
}
}
int n;
char mp[5005][5005];
int cost[5005];
void get_cost(int line,int len){
int now = 1;
int ans = 0;
while( now <= n ){
while( now <= n and mp[line][now] == '0' )now ++;
if( now > n )break;
ans ++;now += len;
}
SPFA::add( line,len + 5000,1,ans * cost[len] );
}
int main(){
cin >> n;
SPFA::init( 10002,10003 );
for(int i = 1;i <= n;i ++)scanf( "%s", mp[i] + 1 );
for(int i = 1;i <= n;i ++)scanf( "%d", &cost[i] );
for(int i = 1;i <= n;i ++)SPFA::add( 10002,i,1,0 ),SPFA::add( i + 5000,10003,1,0);
for(int i = 1;i <= n;i ++)for(int j = 1;j <= n;j ++)get_cost(i,j);
SPFA::get_ans();
cout << SPFA::Cost ;
}
P.S.由于可以转模成原题,我称之为原题改编.
T5 Cirno的寒食节
难度:HAOI D1T1 ?(大雾
因为似乎HA省选最简单?然后这题难度似乎高出普及和提高,所以就和省选沾个边吧qwq.这也是这套水题的主要难度所在。
原题魔改(我的原话.
原题是HN集训还是YL集训,不记得去了,的某一场的T1,原题是一个可以Hash乱搞的简单字符串模式匹配题,当时就想到正解KMP的出题人表示很不满,于是把它改成了多模式串匹配,然后就顺理成章的用上了AC自动机,Hash乱搞的可以byebye了。当然这种允许置换的魔性字符串,AC自动机要魔改,具体地,fail指针的构造方式是不一样的。
然后我们从头讲起,不难想到,置换就是将原串改称某一个字符与前一个相同字符的距离,这就是原题正解。然后考虑出现第一次的字符,我们定义他为0,但是当他失配时,他后一个相同的字符就会变为0,所以在建立fail指针时要特殊照顾一下0的失配,具体的,记录一下某个点在自动机中的深度,然后回跳时判断自己的字符(已改成与前一个相同字符的距离)是否大于深度,否则就改为0.
然后匹配出所有的段,然后对所有段的两端点取出,然后扫描线上贪心的选灯笼,就可以AC了。
STD:
#include<bits/stdc++.h>
using namespace std;
struct node{
int p,type;
node(int _p,int _t) : p(_p), type(_t) {}
bool operator <(const node A)const {
return p < A.p;
}
};
vector<node>line;
stack<int>unco;
bool co[100005];
bool in[100005];
int que[500005][2];
int pt;
namespace AC{
map<int,int>to[500005];
int fail[500005];
int deep[500005];
bool val[500005];
bool mark[500005];
int cnt;
int k;
void insert( int *s,int len ){
int now = 0;
for(int i = 1;i <= len;i ++){
int v = s[i];
if( i == s[i] + 1 )mark[now] = true;
if( !to[now][v] )to[now][v] = ++cnt;
int next = to[now][v];
deep[next] = deep[now] + 1;
now = next;
}
val[now] = true;
}
int find(int now,int next){
while( now != 0 ){
if( deep[now] < next )next = 0;
if( to[now][next] )return to[now][next];
now = fail[now];
}
if( to[now][next] )return to[now][next];
return 0;
}
void build(){
queue<int>Q;
mark[0] = false;
for(auto next = to[0].begin();next != to[0].end();next ++)Q.push( (*next).second );
while( !Q.empty() ){
int now = Q.front();Q.pop();
for(auto next = to[now].begin();next != to[now].end();next ++){
int v = (*next).first;
int s = mark[now] ? 0 : v;
fail[ (*next).second ] = find( fail[now],s );
Q.push( (*next).second );
}
}
}
void query( int *s,int len ){
int now = 0;
for(int i = 1;i <= len;i ++){
int v = s[i];
now = find( now,v );
for( int t = now;t != 0;t = fail[t]){
if( val[t] )que[ ++ pt ][0] = i - deep[t] + 1;que[ pt ][1] = i;
}
}
}
}
int partten[100005];
int last[200005];
int turn[200005];
int tpt = 0;
void deal(int *s,int len,int T){
for(int i = 1;i <= len;i ++){
int ka = s[i];
if( turn[ka] != T )s[i] = 0,turn[ka] = T;
else s[i] = i - last[ ka ];
last[ ka ] = i;
}
}
int main(){
int n;cin >> n >> AC::k;
for(int i = 1;i <= n;i ++)scanf( "%d", partten + i );
int t;cin >> t;
while( t -- ){
static int key[100005];
static int len;scanf("%d",&len);
for(int i = 1;i <= len;i ++)scanf( "%d", key + i );
deal( key,len,++tpt );AC::insert( key,len );
}
AC::build();
deal( partten,n,++tpt );AC::query( partten,n );
for(int i = 1;i <= pt;i ++){
line.push_back( node(que[i][0],i) );
line.push_back( node(que[i][1],i) );
}
sort( line.begin(),line.end() );
int ans = 0;
for(int i = 0;i < ( pt << 1 );){
int nowP = line[i].p;
bool flag = false;
while( line[i].p == nowP ){
int nowT = line[i].type;
if( in[nowT] and (!co[nowT]) )flag = true;
in[nowT] = not in[nowT];
if( in[nowT] )unco.push( nowT );
i ++;
}
if( flag ){
ans ++;
while( !unco.empty() )co[unco.top()] = true,unco.pop();
}
}
cout << ans;
}
后记
这场比赛,无疑是善良的出题人无私的馈赠。精心构造的各种随机的测试数据 能让程序中的错误无处遁形。出题人相信,这个美妙的比赛,可以给拼搏于AK其他官方比赛的逐梦之路上的你,提供一个有力的援助。