说在前面
并没有什么想说的,但是要保持格式=w=
CSDN最近一直换界面,BUG频出不止……
还me代码缩进!!
Kieszonkowe
题面
给定
N
N
个数,请从中选出若干个数,使得总和为偶数,请最大化这个总和
范围:
解法
撒币贪心qwq
下面是代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int N , oddcnt , ans , minodd = 20031109 ;
int main(){
scanf( "%d" , &N ) ;
for( int i = 1 , t ; i <= N ; i ++ ){
scanf( "%d" , &t ) , ans += t ;
if( t&1 ){
oddcnt ++ ;
minodd = min( minodd , t ) ;
}
} if( N == 1 && oddcnt == 1 ) puts( "NIESTETY" ) ;
else printf( "%d" , ans - ( oddcnt&1 ) * minodd ) ;
}
Równanie
题面
对于一个正整数
n
n
,定义为它十进制下每一位数字的平方的和。现在给定三个正整数
k,a,b
k
,
a
,
b
,请求出满足
a≤n≤b
a
≤
n
≤
b
且
k∗f(n)=n
k
∗
f
(
n
)
=
n
的
n
n
的个数
范围:
解法
第一眼以为是数位dp
然而限制这么强,直接都是等号了……
撒币枚举qwq
下面是代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int ans ;
long long K , A , B ;
bool check( long long n , int sums ){
while( n ){
int t = n%10 ; n /= 10 ;
sums -= t * t ;
} return sums == 0 ;
}
int main(){
scanf( "%lld%lld%lld" , &K , &A , &B ) ;
for( int fn = 0 ; fn <= 81 * 18 ; fn ++ ){
if( K * fn > B ) break ;
if( K * fn < A ) continue ;
ans += check( K * fn , fn ) ;
} printf( "%d" , ans ) ;
}
Siano
题面
农夫Byteasar买了一片
N
N
亩的土地,他要在这上面种草。
他在每一亩土地上都种植了一种独一无二的草,其中,第 亩土地的草每天会长高
a[i]
a
[
i
]
厘米。
Byteasar一共会进行
M
M
次收割,其中第 次收割在第
d[i]
d
[
i
]
天,并把所有高度大于等于
b[i]
b
[
i
]
的部分全部割去。Byteasar想知道,每次收割得到的草的高度总和是多少,你能帮帮他吗?不能
范围:
a[i]≤106
a
[
i
]
≤
10
6
,
d[i],b[i]≤1012
d
[
i
]
,
b
[
i
]
≤
10
12
,
N,M≤500000
N
,
M
≤
500000
约定:保证割草的时间递增,且任意时刻草的高度不超过
1012
10
12
解法
其实大体的思路是不难想的,只是实现的时候,需要选择合适的方法
首先这些草,顺序是无所谓的,为了方便维护,我们显然可以将所有草按照生长速度排序
然后可以发现,一次就会割掉一段后缀,并且把后缀的高度全部改成一个值,并且获得多余部分的收益
不难想到用线段树维护
线段树呢,在修改的时候,顺便就把时间以及高度带入进去,进一个区间,就把那个区间的信息更新到现在时刻。然后这样的话,判断割草和计算贡献都很方便
下面是自带大常数的代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int N , M , a[1000005] ;
struct Node{
long long sumV , date , sumH ;
long long minH , maxH , flag , flag_date ;
int lf , rg ;
Node *ch[2] ;
void change_Date( long long now ){
if( now == date ) return ;
sumH += ( now - date ) * sumV ;
minH += ( now - date ) * a[lf] ;
maxH += ( now - date ) * a[rg] ;
date = now ;
}
void update(){
sumH = ch[0]->sumH + ch[1]->sumH ;
minH = ch[0]->minH , maxH = ch[1]->maxH ;
}
void pushdown(){
if( !flag_date ) return ;
ch[0]->flag_date = ch[0]->date = flag_date ;
ch[1]->flag_date = ch[1]->date = flag_date ;
ch[0]->sumH = flag * ( ch[0]->rg - ch[0]->lf + 1 ) ;
ch[1]->sumH = flag * ( ch[1]->rg - ch[1]->lf + 1 ) ;
ch[0]->flag = ch[0]->minH = ch[0]->maxH = flag ;
ch[1]->flag = ch[1]->minH = ch[1]->maxH = flag ;
flag_date = 0 ;
}
}*root , w[2000005] , *tw = w ;
Node *build( int lf , int rg ){
Node *nd = ++tw ;
if( lf == rg ){
nd->lf = nd->rg = lf ;
nd->sumV = a[lf] ; return nd ;
} int mid = ( lf + rg ) >> 1 ;
nd->ch[0] = build( lf , mid ) ;
nd->ch[1] = build( mid+1,rg ) ;
nd->sumV = nd->ch[0]->sumV + nd->ch[1]->sumV ;
nd->lf = lf , nd->rg = rg ;
return nd ;
}
long long Modify( Node *nd , int lf , int rg , long long date , long long H ){
nd->change_Date( date ) ;
if( nd->maxH <= H ) return 0 ;
if( nd->minH >= H ){
long long rt = nd->sumH - H * ( rg - lf + 1 ) ;
nd->sumH = H * ( rg - lf + 1 ) ;
nd->minH = nd->maxH = nd->flag = H ;
nd->flag_date = date ;
return rt ;
} int mid = ( lf + rg ) >> 1 ;
nd->pushdown() ;
long long rt = Modify( nd->ch[0] , lf , mid , date , H ) +
Modify( nd->ch[1] , mid+1,rg , date , H ) ;
nd->update() ;
return rt ;
}
void solve(){
long long b , d ;
for( int i = 1 ; i <= M ; i ++ ){
scanf( "%lld%lld" , &d , &b ) ;
printf( "%lld\n" , Modify( root , 1 , N , d , b ) ) ;
}
}
int main(){
scanf( "%d%d" , &N , &M ) ;
for( int i = 1 ; i <= N ; i ++ ) scanf( "%d" , &a[i] ) ;
sort( a + 1 , a + N + 1 ) ;
root = build( 1 , N ) ; solve() ;
}
Fibonacci
题面
众所周知,斐波那契数列F满足:
f0=0,f1=1,fm=fm−1+fm−2(m≥2)
f
0
=
0
,
f
1
=
1
,
f
m
=
f
m
−
1
+
f
m
−
2
(
m
≥
2
)
现在给出一个数字串
S
S
,请找到一个 使得
fk
f
k
以
S
S
为结尾
(特别的,如果范围内没有答案,输出NIE)
范围:
|S|≤18
|
S
|
≤
18
解法
看到这个题呢,并没有什么很明显的算法偏向
me就在想,如果只看Fibonacci数列的末尾几位数,它这个东西可能具有循环节
于是me打了一个30的表,并没有发现末尾位有循环= =…
然后就去看了题解……
然后是这样的:Fib数列在模
10m
10
m
意义下,具有长度为
6∗10m
6
∗
10
m
的循环节(所以题目上那个
10100
10
100
就是吓人用的)
这显然可以通过定义
f[i][j]
f
[
i
]
[
j
]
表示,在模某个数字的意义下,当前为
i
i
,上一个为 打表得到(打表打少了hhhhh)
然后就可以搜索,从低位到高位按位确定,并用矩阵快速幂来判断当前的Fib是否符合条件就好了
感性上觉得这个算法还是很快的
下面是代码
假装此处有代码
(me懒,并不想写这个题的代码)
Hazard
me最最最讨厌细节题啦!!!QAQ
BZOJ4295传送门
题面
有
N
N
个人在轮流玩赌博机,一开始编号为 的人有
a[i]
a
[
i
]
元钱。赌博机可以抽象为一个长度为
m
m
的仅包含 和
−1
−
1
的序列,若抽到
1
1
,那么你将得到 块钱;若抽到
−1
−
1
,你将输掉
1
1
块钱。
第 局,第
1
1
个人会抽到序列中的第 项;第
2
2
局,第 个人会抽到序列中的第
2
2
项;第 局,第
3
3
个人会抽到序列中的第 项……即:第
i
i
个人抽完后轮到第 个人去抽,特别地,第
n
n
个人抽完后轮到第 个人去抽。序列第
i
i
项被抽到之后,下一个被抽到的将会是第 项,特别地,序列第
m
m
项被抽到之后,下一个被抽到的将会是第 项。
如果在某一轮,有个人输光了所有的钱,那么这场赌博游戏就会结束,请求出游戏在哪一轮结束,或者判断这个游戏会永远进行下去
范围:
N,M,a[i]≤1000000
N
,
M
,
a
[
i
]
≤
1000000
解法
这题细节多的....
首先很显然的,这个抽奖应该具有循环节
然后还可以发现,如果是
n
n
个人抽长度为 的序列,那么每个人能抽到的位置是
m/gcd(n,m)
m
/
gcd
(
n
,
m
)
(比如说
6
6
个人抽长为 的序列,第一个人只能抽到
1
1
号或 号)
那么我们可以把这一些人都提出来一起处理(因为他们的抽奖序列是相同的,只是开始位置不同)
一个人抽到没有钱,要么是因为:一个循环的总和为负,经过几个循环之后在某一个位置停下来;要么就是在第一个循环就停下来
大概把这个序列画一个图出来,发现我们需要维护「前缀和」以及「从每个位置开始经过一个循环的最低值」(这个可以用单调队列)
另外,为了能够快速的知道「每一个值会在之后的什么位置出现」,需要倒着处理,处理过程中记录每个位置最后的出现位置
大概就是这些吧,反正需要想清楚再写
下面是代码
#include <cmath>
#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define las(x) las[x+1000000]
using namespace std ;
const int maxn = 1000005 ;
char ss[maxn] ;
int N , M , a[maxn] , b[maxn] ;
bool vis[maxn] ;
int id[maxn] , que[2*maxn] , fr , ba ;
int nb[maxn] , sum[2*maxn] , mn[maxn] , las[2*maxn] ;
void solve(){
int len , All ;
long long ans = 1LL << 56 ;
for( int i = 0 ; !vis[i] && i < M ; i ++ ){
//get sequence
len = 0 ;
for( int j = i ; !vis[j] ; j = ( j + N )%M ){
nb[++len] = b[j] , vis[j] = true , id[len] = j ;
// printf( "%d " , nb[len] ) ;
}
// puts( "" ) ;
//get prefix_min_value & prefix_sum
fr = 1 , ba = 0 ;
for( int j = 1 ; j <= len ; j ++ ){
sum[j] = sum[j-1] + nb[j] ;
while( ba >= fr && sum[ que[ba] ] >= sum[j] ) ba -- ;
que[++ba] = j ;
} for( int j = 1 ; j <= len ; j ++ ){
while( que[fr] < j - 1 ) fr ++ ;
mn[j] = min( sum[ que[fr] ] - sum[j-1] , 0 ) ;
// printf( "mn[%d] = %d\n" , j , mn[j] ) ;
sum[j+len] = sum[j+len-1] + nb[j] ;
while( ba >= fr && sum[ que[ba] ] >= sum[j+len] ) ba -- ;
que[++ba] = j + len ;
} All = - sum[len] ;
//calc_ans
for( int j = 2 * len ; j > len ; j -- ) las( sum[j] ) = j ;
for( int j = len ; j ; j -- ){
// printf( "j = %d:\n" , j ) ;
las( sum[j] ) = j ;
for( int k = id[j] ; k < N ; k += M ){
// printf( "k = %d:\n" , k ) ;
int val = a[k] ;
if( val + mn[j] > 0 ) { // will not GG at fitst round
if( All <= 0 ) continue ;
long long rd = ceil( 1.0 * ( val + mn[j] ) / All ) , tmp = rd * len ;
tmp += las( sum[j-1] - ( val - rd * All ) ) - j ;
// printf( "rd = %lld , tmp = %lld\n" , rd , tmp ) ;
ans = min( ans , tmp * N + k + 1 ) ;
} else {
ans = min( ans , 1LL * N * ( las( sum[j-1] - val ) - j ) + k + 1 ) ;
// printf( "pos:: %d \n" , las( sum[j-1] - val ) ) ;
}
} //puts( "") ;
}
} printf( "%lld" , ans == ( 1LL << 56 ) ? -1 : ans ) ;
}
int main(){
scanf( "%d" , &N ) ;
for( int i = 0 ; i < N ; i ++ ) scanf( "%d" , &a[i] ) ;
scanf( "%d" , &M ) ; scanf( "%s" , ss ) ;
for( int i = 0 ; i < M ; i ++ ) b[i] = ( ss[i] == 'W' ? 1 : -1 ) ;
solve() ;
}
Mistrzostwa
题面
给定一张 n n 个点 条边的无向图,请找到一个点数最多的点集 S S ,满足:
- 对于点集中任何一个点,它至少与 个点集中的点相邻
- 该点集连通
输出字典序最小的解(当me写到这里时,还没有SPJ,因此只有这样才能过)
范围: n,m≤200000,d<n n , m ≤ 200000 , d < n
约定:无自环解法
直接用个队列,把点度小于 d d <script type="math/tex" id="MathJax-Element-80">d</script> 的点全删了
然后dfs找合法连通块就ok下面是代码
#include <cstdio> #include <cstring> #include <algorithm> #define GG return (void)puts( "NIE" ) ; using namespace std ; int N , M , D , deg[200005] , tp , head[200005] ; struct Path{ int pre , to ; }p[400005] ; void In( int t1 , int t2 ){ deg[t1] ++ , deg[t2] ++ ; p[++tp] = ( Path ){ head[t1] , t2 } ; head[t1] = tp ; p[++tp] = ( Path ){ head[t2] , t1 } ; head[t2] = tp ; } bool vis[200005] ; int que[200005] , ans[200005] , ans_cnt ; int sta[200005] , topp ; void dfs( int u ){ vis[u] = true , sta[++topp] = u ; for( int i = head[u] ; i ; i = p[i].pre ){ int v = p[i].to ; if( !vis[v] ) dfs( v ) ; } } void solve(){ int fr = 1 , ba = 0 ; for( int i = 1 ; i <= N ; i ++ ) if( deg[i] < D ) que[++ba] = i , vis[i] = true ; while( ba >= fr ){ int u = que[fr++] ; for( int i = head[u] ; i ; i = p[i].pre ){ int v = p[i].to ; deg[v] -- ; if( !vis[v] && deg[v] < D ) que[++ba] = v , vis[v] = true ; } } for( int i = 1 ; i <= N ; i ++ ) if( !vis[i] ){ topp = 0 , dfs( i ) ; if( topp > ans_cnt ){ ans_cnt = topp ; memcpy( ans , sta , ( topp + 2 ) * sizeof( int ) ) ; } } if( !ans_cnt ) GG ; printf( "%d\n" , ans_cnt ) ; sort( ans + 1 , ans + ans_cnt + 1 ) ; for( int i = 1 ; i <= ans_cnt ; i ++ ) printf( "%d " , ans[i] ) ; } int main(){ scanf( "%d%d%d" , &N , &M , &D ) ; for( int i = 1 , u , v ; i <= M ; i ++ ){ scanf( "%d%d" , &u , &v ) ; In( u , v ) ; } solve() ; }