博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
AtCoder 杂题训练
阅读量:4677 次
发布时间:2019-06-09

本文共 31225 字,大约阅读时间需要 104 分钟。

前言:

因为要普及了,今年没一等就可以退役去学文化课了,所以暑假把历年noip普及组都刷了一遍,离noip还有50+天,想弄点强化训练什么的。

想了想,就这些天学文化课之余有空就把AtCoder之前那些ARC 的 C D E 什么的刷一下吧(一般是D,可能会有简单一点的E和难一点的C)(可能会很慢,毕竟基本有时间也就周末了)

所以就开了这个坑鞭策一下自己,上个坑是dp的,开了50题,补的巨累...这次吸取教训,只开20题...(也没那么多时间去刷题了)

不会说题意,题意的话可以上网找或者去原站看,只会简述做法以及附上代码


 目前20/20


 1.[AtCoder ARC059D/ABC043D]Unbalanced

总结:结论题

一找就是一道神题...

知道结论后觉得挺简单挺显然的,但是不知道结论的时候真的死活想不出来

对于一个不平衡的串,显然它至少有一个子串是这个样子的“$XX$” ,"$XYX$",$X$和$Y$分别代表不同的字母

然后这样的子串也是最简的不平衡的串。

所以只要在原串中$O(n)$扫一遍,找出形如$XX$,$XYX$的串就可以了(有SPJ)(但是我并不会证明为什么在一个不平衡的串里面一定会有形如$XX$,$XYX$的子串,如果有哪位大大会证可以在评论区里说一下吗QAQ)

#include 
using namespace std ;char s[ 100010 ] ;int main() { scanf( "%s" , s + 1 ) ; int n = strlen( s + 1 ) ; for( int i = 1 ; i <= n ; i ++ ) { if( s[ i ] == s[ i + 2 ] ) { printf( "%d %d\n" , i , i + 2 ) ; return 0 ; } if( s[ i ] == s[ i + 1 ] ) { printf( "%d %d\n" , i , i + 1 ) ; return 0 ; } } puts( "-1 -1" ) ; return 0 ;}
arc059D

 2.[AtCoder ARC059 E]Children and Candies

总结:dp+前缀和优化

好像不是特别难的样子...不过一大堆sigma我一开始看着有点萌币

其实就是当你这个人拿了x个糖果之后呢,前面的方案也就要相应的乘上$\sum_{i=a[i]}^{b[i]} i^m$,所以这个转移是$O(n^4)$的

不过因为$ai,bi$都是固定的所以直接前缀和优化一下就行了

数据也就$400$,$O(n^3)$能过了

但是注意转移时的取模,我之前没搞好$wa$了好几个点...

#include 
using namespace std ; #define N 500#define ll long longconst ll mod = 1e9+7 ; ll n , m ;ll a[ N ] , b[ N ] ;ll sum[ N ][ N ] , l[ N ][ N ] ;ll f[ N ][ N ] ;//f[ i ][ j ]前i个人一共分了j个糖果 //l[ i ][ j ]储存i的j次方//sum[ i ][ j ]对l维护前缀和 int main() { scanf( "%lld%lld" , &n ,&m ) ; for( int i = 1 ; i <= n ; i ++ ) { scanf( "%lld" , &a[ i ] ) ; } for( int i = 1 ; i <= n ; i ++ ) { scanf( "%lld" , &b[ i ] ) ; } for( int i = 1 ; i < N ; i ++ ) { l[ i ][ 0 ] = 1 ; for( int j = 1 ; j < N ; j ++ ) { l[ i ][ j ] = l[ i ][ j - 1 ] * i ; l[ i ][ j ] %= mod ; } } for( int i = 1 ; i < N ; i ++ ){ for( int j = 0 ; j < N ; j ++ ) { sum[ i ][ j ] += sum[ i - 1 ][ j ] + l[ i ][ j ] ; sum[ i ][ j ] %= mod ; } } f[ 0 ][ 0 ] = 1 ; for( int i = 1 ; i <= n ; i ++ ) { for( int j = 0 ; j <= m ; j ++ ) { for( int k = 0 ; k <= j ; k ++ ) { f[ i ][ j ] = ( f[ i ][ j ] % mod + ( 1ll * f[ i - 1 ][ j - k ] *( ( sum[ b[ i ] ][ k ] - sum[ a[ i ] - 1 ][ k ] ) % mod ) + mod ) % mod + mod ) % mod ; } } } printf( "%lld\n" , f[ n ][ m ] ) ; return 0 ;}
arc059E

 3.[AtCoder ARC059F]Unhappy Hacking

总结:dp+乘法逆元

这个F题是假的吧...感觉没有F题难度

就设$f[i][j]$表示按了$i$次键盘,然后一共出现了$j$个字母的情况

刚开始愣是想不到怎么转移,因为可以$f[i][j]$可以从好几个状态转移过来...

秒了一眼题解发现可以用$f[i][j]$去更新别人...所以就转移两次就行了

$f[i+1][j+1]+=2*f[i][j]$(因为可以按0或者1,所以要乘2)

$f[i+1][min(j-1,0)]+=f[i][j]$(这里是按后退键的情况,因为0也是合法状态所以要算进去,取个$min$)

但是这样子算出来的$f[n][len]$是能够用$n$次操作拼出长度为$len$的字符串的方法总数(一共可以拼出$2^{len}$个串),所以要除一下$2^{len}$

但是因为有取模所以要用一下乘法逆元,这里用快速幂来求乘法逆元

#include 
using namespace std ;#define N 5010#define ll long longconst int mod = 1e9+7 ;int n ;char s[ N ] ;int f[ N ][ N ] ;//f[ i ][ j ] 表示按了i次键盘,出现了j个字母 ll power( ll a , ll b ) { ll ans = 1 , base = a ; while( b ) { if( b&1 ) ans = ( ans * base ) % mod ; base = ( base * base ) % mod ; b >>= 1 ; } return ans % mod ;}int main() { scanf( "%d%s" , &n , s+1 ) ; int len = strlen( s + 1 ) ; f[ 0 ][ 0 ] = 1 ; for( int i = 0 ; i <= n ; i ++ ) { for( int j = 0 ; j <= i ; j ++ ) { f[ i + 1 ][ j + 1 ] += 2 * f[ i ][ j ] ; f[ i + 1 ][ j + 1 ] %= mod ; f[ i + 1 ][ max( j - 1 , 0 ) ] += f[ i ][ j ] ; f[ i + 1 ][ max( j - 1 , 0 ) ] %= mod ; } } ll ans =1ll*power( power( 2 , len ) , mod - 2 ) % mod * f[ n ][ len ] ; ans %= mod ; printf( "%lld" , ans ) ; return 0 ;}
arc059F

 4.[AtCoder ARC060C]Tak and Cards

总结:dp

一开始只能想到爆搜的做法...

我们可以设$f[i][j][k]$表示前$i$个人中选了$j$个人,他们的平均数为$k$的最优解

那么初始化$f[0][0][0]=1$

转移方程:

$f[i][j][k]+=f[i-1][j][k]$(不取)

$f[i][j][k]+=f[i-1][j-1][k-a[i]]$(取)

就类似于背包那样子去搞

#include 
using namespace std ; int n , ar , a[ 60 ] ;long long f[ 60 ][ 60 ][ 2510 ] ;//选到i,选了j个,sum为k int main() { scanf( "%d%d" , &n , &ar ) ; for( int i = 1 ; i <= n ; i ++ ) { scanf( "%d" , &a[ i ] ) ; } f[ 0 ][ 0 ][ 0 ] = 1 ; for( int i = 1 ; i <= n ; i ++ ) { for( int j = i ; j >= 0 ; j -- ) { for( int k = ar * n + 1 ; k >= 0 ; k -- ) { f[ i ][ j ][ k ] += f[ i - 1 ][ j ][ k ] ; if( k >= a[ i ] && j ) f[ i ][ j ][ k ] += f[ i - 1 ][ j - 1 ][ k - a[ i ] ] ; } } } long long ans = 0 ; for( int i = 1 ; i <= n ; i ++ ) { ans += f[ n ][ i ][ ar * i ] ; } printf( "%lld\n" , ans );}
ARC060C

 5.[AtCoder ARC060D]Digit Sum

总结:数学题

被数学题虐爆...研究了好久的题解才明白这题...

题意:给一个函数$f(b,n)$可以求出$n$在$b$进制下各位数的和,设$f(b,n)=s$,现在已知$n,s$,求$b$

我们可以分成两种情况来讨论

当$b<=\sqrt{n}$时,我们可以直接枚举$b$,然后套用题目的公式来计算

当$b>\sqrt{n}$时,我们可以寻找一下规律:

设$n$在$b$进制下的高位为$p$低位为$q$(因为$b>\sqrt{n}$,所以这里的$n$在$b$进制下一定是两位数)

这时的$n=pb+q$ , $s=p+q$

所以$n-s=(b-1)p$

即$$b=\frac{n-s}{p}+1$$

因为$b$是整数,所以枚举$n-s$的所有因数$p$就可以了,不过枚举之后要更新答案的时候记得再套一遍$f(b,n)$来检查一下,因为算出来的$b$可能会出现某些奇奇怪怪的东西(亲测...)

复杂度是$O(\sqrt{n})$的

#include 
using namespace std ;#define inf 0x3f3f3f3f#define ll long longll n , s ;ll f( ll b , ll n ) { return n < b ? n : f( b , n / b ) + n % b ;}int main() { scanf( "%lld%lld" , &n , &s ) ; if( s > n ) return puts( "-1" ) , 0 ; if( s == n ) return printf( "%lld\n" , n + 1 ) , 0 ; ll m = sqrt( n ) + 1 ; for( ll i = 2 ; i <= m ; i ++ ) { if( f( i , n ) == s ) return printf( "%lld" , i ) , 0 ; } ll ans = 1e11 ; n -= s ; for( ll i = 1 ; i * i <= n ; i ++ ) { if( n % i == 0 ) { ll b = n / i + 1; if( f ( b , n + s ) == s ) ans = min ( ans , b ) ; } } printf( "%lld\n" , ans != 1e11 ? ans : -1 ) ; return 0 ;}
ARC060D

6.[AtCoder ARC060E]Tak and Hotels

总结:分块+二分

首先用二分求出每个点每一天能够走到哪个点(贪心地想,能走更远肯定走更远,至于如果走过了,肯定在那天的一半的时候就走到目的地了,所以时间花费还是一样的)

然后我一开始是只处理出这个然后就一个个去跳了...结果只$A$了第一个点,其他全部$TLE$

所以要考虑优化,想了好久忽然想起弹飞绵羊那题,这题除了没有修改操作其实是和弹飞绵羊差不多的

所以大力分块!

二分预处理不变,然后对原序列分块。再维护一下每个点跳出该块后在哪以及跳出该块要花多少天

在查询的时候就可以一个块一个块的跳跳到目的地所在的那一块,剩下的路径暴力就可以,这样复杂度最坏是$O(2\sqrt{n})$的

复杂度是$O(nlogn+q\sqrt{n})$的,AtCoder的时限三秒,很松,能过

我写完后上网看了一下别人的做法怎么清一色的倍增...

#include 
using namespace std ;#define N 100010int n , L , m ;int a[ N ] , to[ N ] ;int num , block , belong[ N ] ; int nxt[ N ] , val[ N ] ;int find( int x ) { int l = x + 1 , r = n , ans = 0 ; while( l <= r ) { int mid = ( l + r ) >> 1 ; if( a[ mid ] - a[ x ] <= L ) l = mid + 1 , ans = mid ; else r = mid - 1 ; } return ans ;}int main() { scanf( "%d" , &n ) ; for( int i = 1 ; i <= n ; i ++ ) { scanf( "%d" , &a[ i ] ) ; } sort( a + 1 , a + n + 1 ) ; scanf( "%d%d" , &L , &m ) ; for( int i = 1 ; i < n ; i ++ ) { to[ i ] = find( i ) ; } to[ n ] = n + 1 ; block = sqrt( n ) ; num = n / block ; if( n % block ) num ++ ; for( int i = 1 ; i <= n ; i ++ ) { belong[ i ] = ( i - 1 ) / block + 1 ; } for( int i = 1 ; i < n ; i ++ ) { int t = i , sp = 0 ; while( belong[ t ] == belong[ i ] ) { t=to[ t ] ; sp ++ ; } val[ i ] = sp ; nxt[ i ] = t ; if( i >= block * ( num - 1 ) + 1 ) val[ i ] -- , nxt[ i ] -- ; } for( int i = 1 ; i <= m ; i ++ ) { int x , y , ans = 0 ; scanf( "%d%d" , &x ,&y ) ; if( x > y )swap( x , y ) ; while( belong[ x ] < belong[ y ] ) { ans += val[ x ] ; x = nxt[ x ] ; } while( x < y ) { x = to[ x ] ; ans ++ ; } printf( "%d\n" , ans ) ; } return 0 ;}
ARC060E

 7.[AtCoder ARC061D]Snuke's Coloring

总结:map

很明显一个点被染色只有可能对周围的点产生影响,所以暴力修改就行了,至于怎么暴力修改,当然是用$map$啊(大雾),虽然范围到$1e9$但是点只有$1e5$,所以用一个$map$存

然后0的点可以用数学方法算出来$(h-2)*(w-2)$(小学知识?

但是并不会遍历$map$所以专门去学了一下...

#include 
using namespace std ;#define ll long longmap
,int>mp;int h , w , n , cnt[ 10 ] ;int main(){ scanf( "%d%d%d" , &h , &w , &n ) ; while( n -- ) { int x , y ; scanf( "%d%d" , &x , &y ) ; for( int i = max( 1 , x - 2 ) ; i <= min( h - 2 , x ) ; i ++ ) { for( int j = max( 1 , y - 2 ) ; j <= min( w - 2 , y ) ; j ++ ) { mp[ pair
(i,j) ] ++ ; } } } map< pair
, int > :: iterator iter ; iter = mp.begin() ; while( iter != mp.end() ) { cnt[ iter -> second ] ++ ; iter ++ ; n ++ ; } ll ans = 1ll * ( h - 2 ) * ( w - 2 ) ; printf( "%lld\n" , ans - n - 1 ) ; for( int i = 1 ; i <= 9 ; i ++ ) { printf( "%d\n" , cnt[ i ] ) ; } return 0 ;}
ARC061D

 8.[AtCoder ARC061E]Snuke's Subway Trip

总结:spfa/dijkstra+拆边

这题我卡了两天...然后wa了要有差不多20次...不断逼近正解

首先显然是最短路,但是边权是会变的。如果你熟悉最短路的模型的话,大概是可以想出来在更新距离数组$d$的时候,顺便维护一下一个$now$数组表示上次最优解走到这里是用的哪家铁路

但是这样会挂掉1半的点:你可能有多种方法到这个点且花费相同....所以你需要开个$map$或者$set$来存当前最优解情况下能到这的点有哪些...

注意是双向边...我一开始打的单向边挂的不知所措...

细节很多,一定要注意细节

#include 
using namespace std ;#define N 500010#define inf 0x3f3f3f3fstruct node { int to , nxt , belong ;}e[ N << 2 ];int n , m ; int head[ N << 2 ] , cnt ;int d[ N * 2 ] ;void ins( int u , int v , int c ) { e[ ++ cnt ].to = v ; e[ cnt ].nxt = head[ u ] ; e[ cnt ].belong = c ; head[ u ] = cnt ;}std::set< int > st[ N ] ;priority_queue< pair
> q;void spfa() { for( int i = 1 ; i <= n ; i ++ ) d[ i ] = inf ; d[ 1 ] = 0 ; q.push( { 0 , 1 } ) ; while(!q.empty()){ int u = q.top().second , ans = -q.top().first; q.pop() ; if( d[ u ] < ans ) continue ; for( int i = head[ u ] ; i ; i =e[ i ].nxt ) { int v = e[ i ].to ; int num = ans + !st[ u ].count( e[ i ].belong ) ; if( d[ v ] > num ) { d[ v ] = num ; q.push( { -num , v } ) ; st[ v ].clear() ; st[ v ].insert( e[ i ].belong ) ; }else if( d[ v ] == num ) { st[ v ].insert( e[ i ].belong ) ; } } } if( d[ n ] == inf ) puts( "-1" ) ; else printf( "%d\n" , d[ n ] ) ;}int main() { scanf( "%d%d" , &n ,&m ) ; for( int i = 1 ; i <= m ; i ++ ) { int x , y , c ; scanf( "%d%d%d" , &x , &y , &c ) ; ins( x , y , c ) ; ins( y , x , c ) ; } spfa() ;}
dijkstra

然后我其实一开始打的是spfa的,莫名其妙挂掉了2个点...我也不知道错在哪里...打了dijkstra就过了。代码放在这里有哪位大佬能看下我哪里出错了就在评论区说一下吧。QAQ

#include 
using namespace std ;#define N 500010#define inf 0x3f3f3f3fstruct node { int to , nxt , belong ;}e[ N << 2 ];int n , m ; int head[ N << 2 ] , cnt ;int d[ N * 2 ] , vis[ N * 2 ] ;int q[ N * 10 ] ;void ins( int u , int v , int c ) { e[ ++ cnt ].to = v ; e[ cnt ].nxt = head[ u ] ; e[ cnt ].belong = c ; head[ u ] = cnt ;}std::set< int > st[ N ] ;void spfa() { memset( d , inf , sizeof( d ) ) ; q[ 1 ] = vis[ 1 ] = 1 ; d[ 1 ] = 0 ; int l = 1 , r = 2 ; while( l <= r ) { int u = q[ l ++ ] ; vis[ u ] = 0 ; for( int i = head[ u ] ; i ; i = e[ i ].nxt ) { int v = e[ i ].to , num = d[ u ] + ( !st[ u ].count( e[ i ].belong ) ) ; if( d[ v ] > num ) { d[ v ] = num ; st[ v ].clear() ; st[ v ].insert( e[ i ].belong ) ; if( ! vis[ v ] ) vis[ v ] = 1 , q[ r ++ ] = v ; }else if( d[ v ] == num ) { st[ v ].insert( e[ i ].belong ) ; } } } if( d[ n ] == inf ) puts( "-1" ) ; else printf( "%d\n" , d[ n ] ) ;}int main() { scanf( "%d%d" , &n ,&m ) ; for( int i = 1 ; i <= m ; i ++ ) { int x , y , c ; scanf( "%d%d%d" , &x , &y , &c ) ; ins( x , y , c ) ; ins( y , x , c ) ; } spfa() ;}
spfa(挂了2个点)

网上做法怎么全都是拆边的,貌似很妙的样子,我去学习一下然后来更新一下

 

拆边做法真的很优美呢

我上面的做法真是不优美,所以来更新一个优美一点的做法

我们把单条边拆掉,拆成三条边,中间放两个虚点,规定虚点之间的费用为0,实点到虚点的费用为1,那么就可以直接跑spfa了,最后答案除2就行(这样子拆边的话一条路径的花费是2,所以要除一下)

至于虚点的编号可以用map来取,为什么不能直接$tot++$呢?因为如果直接加点的编号,那么对于同个公司的情况就没法求了——你每一次到达的都是一个新点。

#include 
using namespace std ;#define N 5000010#define inf 0x3f3f3f3fint head[ N ] , cnt ;int d[ N ] , vis[ N ] , q[ N ] ;int n , m ;struct node { int to , nxt , v ;}e[ N ] ; void ins( int u , int v , int w ) { e[ ++ cnt ].to = v ; e[ cnt ].nxt = head[ u ] ; e[ cnt ].v = w ; head[ u ] = cnt ;} map
,int>mp;int tot = 0 ;int get_num( int x , int y ) { if( !mp.count( make_pair( x , y ) ) ) mp[ make_pair( x , y ) ] = ++tot ; return mp[ make_pair( x , y ) ] ;}void spfa() { for( int i = 1 ; i <= tot ; i ++ ) d[ i ] = inf ; vis[ 1 ] = q[ 1 ] = 1 ; d[ 1 ] = 0 ; int l = 1 , r = 2 ; while( l < r ) { int u = q[ l ++ ] ; vis[ u ] = 0 ; for( int i = head[ u ] ; i ; i = e[ i ].nxt ) { int v = e[ i ].to ; if( d[ v ] > d[ u ] + e[ i ].v ) { d[ v ] = d[ u ] + e[ i ].v ; if( !vis[ v ] ) vis[ v ] = 1 , q[ r ++ ] = v ; } } } if( d[ n ] == inf ) puts( "-1" ) ; else printf( "%d\n" , d[ n ] / 2 ) ;}int main() { scanf( "%d%d" , &n , &m ) ; tot = n ; for( int i = 1 ; i <= m ; i ++ ) { int x , y , c ; scanf( "%d%d%d" , &x , &y , &c ) ; int n1 = get_num( x , c ) , n2 = get_num( y , c ) ; ins( x , n1 , 1 ) ; ins( n1 , x , 1 ) ; ins( n1 , y , 1 ) ; ins( y , n1 , 1 ) ; ins( n1 , n2 , 0 ) ; ins( n2 , n1 , 0 ) ; } spfa() ;}
拆边+spfa

 9.[AtCoder ARC061F]Card Game for Three

总结:组合数

这$F$题好难啊...只会部分分做法,下面两个方法都是部分分做法。满分做法我去看看...会的话就补一下

部分分做法

方法1:

首先$A$能赢的条件很明显,假设在所有的牌里面取出$A$张$A$牌,$B$张$B$牌,$C$张$C$牌,那么$A$能赢当且仅当$A=n,B<m,C<k$

所以假设我们在拿出了$n$张$A$牌的情况下,中间穿插着拿了$B$张$B$牌,$C$张$C$牌,则有

$$\sum_{i=0}^{i<=m+k}C(n-1,i+n-1)*3^{m+k-i}*\sum_{j=0}^{j<=m,j<=k}C(i,j)$$

首先在$i+n-1$张牌中取$n-1$张$A$牌的方案为$C(n-1,n+i-1)$

注意:最后一张牌一定需要是$A$,所以就只能有$n-1$

而且剩下的牌数的排列为$3^{m+k-i}$,要乘上去

以及在$i+n-1$中$B$和$C$的排列为$C(i,j)$,也要乘起来

所以就得到了上面的公式

但是,上面的做法我打挂了...这个式子是对的,我算后面那个$\sum_{j=0}^{j<=m,j<=k}C(i,j)$那里我没有枚举好...查不出来啊...

 

我在理解了满分做法之后终于发现我挂在哪里了...

在枚举后面的东西的时候我没有分类讨论。但是改完后貌似还分少了一类...

$subtask_1$10个点我错了$2$个...然后$subtask_2$被我强行水过去$2$个点..

用$C$牌数量来分类:分为$j<k$,$j<m$,$j<i$(第三类我没分...不想去改了...)

对于第一种情况:$x=\sum_{j=0}^{j<k}C(i,j)$

对于第二种情况:$x=\sum_{j=0}^{j<m}C(i,k)$

对于第三种情况:请读者独立思考(知道有第三种情况是因为我去看了一下满分的做法...)

#include 
using namespace std ;const int mod = 1e9 + 7 ;const int N = 900010 ;#define ll long long int n , m , k ;ll fac[ N ] , ifac[ N ] , p[ N ] ; ll mul( ll x , ll y ) { return ( 1ll * x * y ) % mod ;}ll add( ll x , ll y ) { return ( x + y ) % mod ;}ll power( ll a , ll b ) { int ans = 1 , base = a ; while( b ) { if( b&1 ) ans = mul( ans , base ) ; base = mul( base , base ) ; b >>= 1 ; } return ans ;}ll inv( ll x ) { return power( x , mod - 2 ) % mod ;}ll C( ll x , ll y ) { return ( fac[ x ] * ifac[ y ] % mod * ifac[ x - y ] % mod ) % mod ;}int main() { scanf( "%d%d%d" , &n , &m , &k ) ; fac[ 0 ] = 1ll ; p[ 0 ] = 1ll ; for( int i = 1 ; i < N ; i ++ ) { fac[ i ] = fac[ i - 1 ] * i % mod ; p[ i ] = p[ i - 1 ] * 3ll % mod ; } for( int i = 0 ; i < N ; i ++ ) { ifac[ i ] = inv( fac[ i ] ) ; } ll ans = 0 , x = 1ll ; n -- ; for( int i = 0 ; i <= m + k ; i ++ ) { ans = ( ans + C( n + i , n ) * p[ m + k - i ] % mod * x ) % mod ; for( int j = 0 ; j <= min( i , m - 1 ) ; j ++ ) { if( i - j < k ) x = add( x , C( i , j ) ) ; else if( i - j < m ) x = add( x , C( i , i - k + 1 ) ) ; } } printf( "%lld\n" , add( ans , mod ) ) ; return 0 ;}
ARC061F

 

方法2:

换一种想法,在前$i$张卡片中拿出$n$张$A$,$j$张$B$,$C$张$C$

则可以推出一个公式

$$\sum_{i=0}^{i<=n+m+k}\frac{(i-1)!}{(n-1)!j!C!}3^{n+k+m-i}$$

如果看得懂那个方法一的话这个公式大概也是看得懂的吧...

就是$i-1$的全排列数除掉$n-1$的全排列数和$j$的全排列数和$C$的全排列数

这个是很基础的一个组合数的常识,这样子得到的就是我们要的当前情况的方案数,记住也要把当前剩下的那些的方案数也乘上去,即$3^{n+m+k-i}$

然后这里的除法是$mod$ $m$意义下的,所以要求一下逆元,代码中的$fac[i]$即为$i!$的值,$ifac[i]$即为$i!$的逆元

#include 
using namespace std ;#define N 5010#define ll long longconst int mod = 1e9 + 7 ;int n , m , k ;ll fac[ N ] , ifac[ N ] ;ll power( ll a ,ll b ) { ll base = a , ans = 1 ; while( b ) { if( b&1 ) ans = ( ans * base ) % mod ; base = ( base * base ) % mod ; b >>= 1 ; } return ans ;}ll mul( ll x ,ll y ) { return ( 1ll * x * y ) % mod ;}ll inv( ll x ) { return power( x , mod - 2 ) % mod ;}int main() { scanf( "%d%d%d" , &n , &m , &k ) ; fac[ 0 ] = 1 ; for( int i = 1 ; i < N ; i ++ ) fac[ i ] = mul( fac[ i - 1 ] , i ) ; for( int i = 0 ; i < N ; i ++ ) ifac[ i ] = inv( fac[ i ] ) ; ll ans = 0 , sum = n + k + m ; for( int i = n ; i <= sum; i ++ ) {
// 取出n张A牌 for( int j = 0 ; j <= m ;j ++ ) {
//B牌数量 int C = i - n - j ;//C牌数量 if( C < 0 || C > k ) continue ; ll tmp = mul( fac[ i - 1 ] , mul( ifac[ n - 1 ] , mul( ifac[ j ] ,ifac[ C ] ) ) ) ; tmp = mul( tmp , power( 3 , sum - i ) ) ; ans = ( ans + tmp ) % mod ; } } printf( "%lld\n" , ans ) ; }
ARC061F

 

钻研了很久的题解,终于差不多搞懂满分做法了...

满分做法

方法一确实是对的。这个满分做法就是用来改进那里的

观察耗时,耗时基本都是花费在枚举后面那一段,所以考虑优化那一段...(这个做法很清奇...正常写组合数不应该是优化那个式子吗...)

$$\sum_{i=0}^{i<=m+k}C(n-1,i+n-1)*3^{m+k-i}*\sum_{j=0}^{j<=m,j<=k}C(i,j)$$

还是这个式子,我们来优化掉后面那个$\sum_{j=0}^{j<=m,j<=k}C(i,j)$(就是我写错了还查不出来的那个玩意...不!我写到这里的时候忽然发现我在枚举的时候没有分类讨论!)

我们把后面那个$\sum$拆掉,分三部分讨论

假设我们现在手上有$x$张$C$牌

当$x<k$时,随便取...(C的数量都比你要取的多了所以肯定不会超限)即$\sum_{i=0}^{x<k}C(x,i)$

当$x<m$时,$C$的数量在$i$以内,同时不能取超过$k$个,即$\sum_{i=0}^{i<m}C(x,i)$

当$m<=x<m+k$时,$B,C$数量均不超过$i$,同时$B$不能取超过$m$个,$C$不能取超过$k$个,即$\sum_{i-m+1}^{i<k}C(x,i)$

然后怎么优化呢

如果你熟知杨辉三角这个东西的话,大概就会知道怎么优化了

假设我们已经知道了$i-1$时$x$的值,那么其实可以推出下面的几个式子:

情况$1$:$x=x*2$

情况$2$:$x=x*2-C(i,k)$

情况$3$:$x-x*2-C(i,k)-C(i,m)$

这个挺容易推的吧...如果部分分做法的第一种有推出来那么这个优化也就顺理成章了的样子(但是我推出来了一半...)

#include 
using namespace std ;const int mod = 1e9 + 7 ;const int N = 900010 ;#define ll long long int n , m , k ;ll fac[ N ] , ifac[ N ] , p[ N ] ; ll mul( ll x , ll y ) { return ( 1ll * x * y ) % mod ;}ll add( ll x , ll y ) { return ( x + y ) % mod ;}ll power( ll a , ll b ) { int ans = 1 , base = a ; while( b ) { if( b&1 ) ans = mul( ans , base ) ; base = mul( base , base ) ; b >>= 1 ; } return ans ;}ll inv( ll x ) { return power( x , mod - 2 ) % mod ;}ll C( ll x , ll y ) { return ( fac[ x ] * ifac[ y ] % mod * ifac[ x - y ] % mod ) % mod ;}int main() { scanf( "%d%d%d" , &n , &m , &k ) ; fac[ 0 ] = 1ll ; p[ 0 ] = 1ll ; for( int i = 1 ; i < N ; i ++ ) { fac[ i ] = fac[ i - 1 ] * i % mod ; p[ i ] = p[ i - 1 ] * 3ll % mod ; } for( int i = 0 ; i < N ; i ++ ) { ifac[ i ] = inv( fac[ i ] ) ; } ll ans = 0 , x = 1ll ; n -- ; for( int i = 0 ; i <= m + k ; i ++ ) { ans = ( ans + C( n + i , n ) * p[ m + k - i ] % mod * x ) % mod ; if( i < k ) x = ( x * 2ll ) % mod ; else if( i < m ) x = ( x * 2ll - C( i , k ) ) % mod ; else x = ( x * 2ll - C( i , k ) - C( i , m ) ) % mod ; } printf( "%lld\n" , add( ans , mod ) ) ; return 0 ;}
ARC061F满分做法

 

数学题真的虐哭我...这题我研究了$3$天...


 10.[AtCoder ABC110C]String Transformation

总结:模拟

简单题啊...但是我赛时磨了要半小时?想复杂了...

其实对于每个字符,分三类讨论:

$1.$字母都没有出现过的

$2.$对于字母出现过的,如果要变成的字母和之前出现过的相同,那就可以直接跳过

$3.$对于字母出现过的,然后还和你要变成的不同,那么肯定就是$No$了...变不回去的(这个可以自己模拟一下,当时就卡在这里)

#include
using namespace std;const int N = 2e5+5 ;char s1[ N ] , s2[ N ] ;int a[ N ] , b[ N ] ;int main() { scanf( "%s %s" , s1 , s2 ) ; int len1 = strlen( s1 + 1 ) , len2 = strlen( s2 + 1 ) ; if( len1 != len2 ) return puts( "No" ) , 0 ; for( int i = 1 ; i <= len1 ; i ++ ) { if( !a[ s1[ i ] - 'a' ] && !b[ s2[ i ] - 'a' ] ) { a[ s1[ i ] - 'a' ] = s2[ i ] - 'a' ; b[ s2[ i ] - 'a' ] = s1[ i ] - 'a' ; }else if( a[ s1[ i ] - 'a' ] == s2[ i ] - 'a' && b[ s2[ i ] - 'a' ] == s1[ i ] - 'a' ) continue ; else if( a[ s1[ i ] - 'a' ] != s2[ i ] - 'a' || b[ s2[ i ] - 'a' ] != s1[ i ] - 'a' ) return puts("No"),0 ; } return puts("Yes") , 0 ;}
ABC110C

 11.[AtCoder ABC110D]Factorization

总结:组合数,质因数分解

我们可以设$pi$为$m$的所有质因数,则有

$$m=p1^{b1}p2^{b2}...pk^{bk}$$

(质因数分解的常识)

题目的条件是$$a1a2...an=m$$

则有$$ai=p1^{c_{i,1}}p2^{c_{i,2}}...pk^{c_{i,k}}$$

但是对于$n$个$ai$而言,我们求得的这些$pi^{c_{i,j}}$肯定是有重复的,要考虑怎么筛掉这些重复的情况

我们可以设

$$b_j=c_{1,j}+c_{2,j}+c_{3,j}+...+c_{n,j}$$

因为对于一个$i$而言,$ai$和$ai'$不同,当且仅当$c_{i,j}$和$c_{i,j}'$不同

所以我们可以直接去计算这些$bj$

答案即为$$C(b_1+n-1,n-1)*C(b_2+n-1,n-1)*...*C(b_k+n-1,n-1)$$

$b_i$是$m$的质因数$pi$出现的次数

所以效率应该是$O(\sqrt{m}*log(m))$

当然也可以预处理出每个数的阶乘和逆元,那么不算预处理的话复杂度是$O(\sqrt{m})$的,但是预处理了其实会更慢一点...因为如果数组开的比较大的话预处理的时间可能会大于$\sqrt{m}$

#include 
using namespace std ;#define N 1000010#define ll long longconst ll mod = 1e9 + 7 ;ll fac[ N ] ,ifac[ N ] ;ll n , m ;ll q[ N ] , cnt = 0 ;map
mp ;ll power( ll a , ll b ) { ll ans = 1 , base = a ; while( b ) { if( b&1 ) ans = ( ans * base ) % mod ; base = ( base * base ) % mod ; b >>= 1 ; } return ans ;}ll C( ll n , ll m ) { return fac[ m ] * ifac[ n ] % mod * ifac[ m - n ] % mod ;}int main() { scanf( "%lld%lld" , &n , &m ) ; fac[ 0 ] = 1 ; for( int i = 1 ; i < N ; i ++ ) fac[ i ] = ( fac[ i - 1 ] * i ) % mod ; for( int i = 0 ; i < N ; i ++ ) ifac[ i ] = power( fac[ i ] , mod - 2 ) % mod ; ll ln = m , tmp = 2 ; while( ln != 1 && tmp * tmp <= ln ) { if( ln % tmp == 0 ) { mp[ tmp ] ++ ; if( mp[ tmp ] == 1 ) q[ ++ cnt ] = tmp ; ln /= tmp ; }else tmp ++ ; } if( ln != 1 ) { mp[ ln ] ++ ; if( mp[ ln ] == 1 ) q[ ++ cnt ] = ln ; } ll ans = 1 ; for( int i = 1 ; i <= cnt ; i ++ ) { ans = ans * C( n - 1 , mp[ q[ i ] ] + n - 1 ) % mod ; } printf( "%lld\n" , ans ) ; return 0 ;}
ABC110D

 12.[AtCoder ARC062D]AtCoDeer and Rock-Paper

总结:结论题,模拟

就是一个只有石头和布的石头剪刀布...然后任何时候石头都得比剪刀出的次数多

假的$D$题...一个特别好猜的结论,只要能出布那就出布,因为不管怎么样出布都稳赚不赔,所以按这个结论直接模拟就好

拿两个$cur$一个维护布数一个维护石头数就行了

#include 
#include
#include
using namespace std ;#define ll long long#define N 100010char s[ N ] ;int cur1 , cur2 , ans ;//rock和paper,paper赢//同样则不加分 //rock-g , paper-pint main() { scanf( "%s" , s + 1 ) ; int n = strlen( s + 1 ) ; if( s[ 1 ] == 'p' ) ans = -1 ; cur2 = 1 ; for( int i = 2 ; i <= n ; i ++ ) { if( s[ i ] == 'p' ) { if( cur1 < cur2 ) { cur1 ++ ; }else ans -- , cur2 ++ ; }else { if( cur1 < cur2 ) { cur1 ++ ; ans ++ ; }else cur2 ++ ; } } printf( "%d\n" , ans ) ; return 0 ;}
ARC062D

 13.[AtCoder ARC062E]Building Cubes with AtCoDeer

总结:map+hash+暴力

对于一个正方体,显然我们只要知道一个对面就能知道所有的面的颜色。

然后可以把每个正方形的四个放置方式的$hash$值存进$map$里面

接着枚举两个面(假设他们相对)然后就可以算出其他面的颜色,然后乘法原理乘一下就可以了

不好意思,$hash+map$真的是可以为所欲为的

#include 
using namespace std ;#define N 1010#define ll long long int n ;ll c[ N ][ 4 ] ;ll h[ N ] , ans = 0 ;map< ll , ll > mp ;ll hash( ll *a ) { ll res = 0 ; for( int i = 0 ; i < 4 ; i ++ ) { res |= ( a[ i ] << ( i * 10ll ) ) ; } return res ;}void upd( ll x , ll d ) { for( int i = 0 ; i < 4 ; i ++ , x = ( ( x&1023ll ) << 30ll ) | ( x >> 10ll ) ) { mp[ x ] += d ; }}int main() { scanf( "%d" , &n ) ; for( int i = 1 ; i <= n ; i ++ ) { for( int j = 0 ; j < 4 ; j ++ ) { scanf( "%lld" , &c[ i ][ j ] ) ; } h[ i ] = hash( c[ i ] ) ; upd( h[ i ] , 1ll ) ; } for( int i = 1 ; i <= n - 5 ; i ++ ) { upd( h[ i ] , -1ll ) ; for( int j = i + 1 ; j <= n ; j ++ ) { upd( h[ j ] , -1ll ) ; for( int a = 0 ; a < 4 ; a ++ ) { ll res = 1ll , val[ 4 ] ; bool check = 0 ; for( int b = 0 ; b < 4 ; b ++ ) { ll x[] = { c[ i ][ ( b + 1 ) & 3 ] , c[ i ][ b ] , c[ j ][ ( 3 - b + a ) & 3 ] , c[ j ][ ( 6 - b + a ) & 3 ] } ; val[ b ] = hash( x ) ; if( !mp.count( val[ b ] ) ) { check = 1 ; break ; } } if( !check ) { for( int k = 0 ; k < 4 ; k ++ ) { res = res * mp[ val[ k ] ] ; upd( val[ k ] , -1ll ) ; } ans += res ; for( int k = 0 ; k < 4 ; k ++ ) { upd( val[ k ] , 1ll ) ; } } } upd( h[ j ] , 1ll ) ; } } printf( "%lld" , ans ) ;}
ARC062E

 14.[AtCoder ARC063D]An Invisible Hand

总结:递推

容易发现因为不能往回走,所以这个贸易其实就是选两个点使得他们的差值最大。要降低利润就是要么给起点$+1$要么给终点$-1$

那么我们只要扫一遍找出这个差值,再扫一遍找出差值为这个最大差值的个数就行

总的复杂度$O(n)$

#include 
using namespace std ;#define N 100010#define inf 0x3f3f3f3f#define ll long longint n , t ;int a[ N ] ;int main() { scanf( "%d%d" , &n , &t ) ; for( int i = 1 ; i <= n ; i ++ ) scanf( "%d" ,&a[ i ] ) ; int ans = 0 , mx = 0 ; int tot = 0 ; for( int i = n ; i ; i -- ) { mx = max( a[ i ] , mx ) ; ans = max( mx - a[ i ] , ans ) ; } mx = 0 ; for( int i = n ; i ; i -- ) { mx = max( a[ i ] , mx ) ; if( mx - a[ i ] == ans ) tot ++ ; } printf( "%d\n" , tot ) ; return 0 ;}
ARC063D

 15.[AtCoder ARC063E]Integers on a Tree

总结:堆+递推

唔...这道题好像是挺经典的题目?(可能是我记错了)

反正就是枚举每个点,向旁边递推,他们的差值的绝对值必须为$1$,对于没有放数字的点,我们显然可以用现在正在用来推的这个点$u$来更新,但是我们怎么知道要$+1$还是$-1$呢

这时候就要用到堆了,用一个小根堆来保证我们是从小的数开始递推的,那么对于没有放数字的点,我们只要直接更新就好了$d[v]=d[u]+1$

如果发现一条路径上的两个点差值的绝对值不为$1$就直接输出$No$就行了

#include 
using namespace std ;#define N 100010#define inf 0x3f3f3f3fint n , K , d[ N ] ;int head[ N << 1 ] , cnt , vis[ N ] ;struct edge { int to , nxt ;}e[ N << 1 ] ; priority_queue< pair
, vector< pair
> , greater< pair
> > q ;void ins( int u , int v ) { e[ ++ cnt ].to = v ; e[ cnt ].nxt = head[ u ] ; head[ u ] = cnt ;}int main() { for( int i = 0 ; i < N ; i ++ ) d[ i ] = inf ; scanf( "%d" , &n ) ; for( int i = 1 ; i < n ; i ++ ) { int u , v ; scanf( "%d%d" , &u , &v ) ; ins( u , v ) ; ins( v , u ) ; } scanf( "%d" , &K ) ; for( int i = 1 ; i <= K ; i ++ ) { int p , v ; scanf( "%d%d" , &v , &p ) ; d[ v ] = p ; q.push( make_pair( p , v ) ) ; } while( !q.empty() ) { int u = q.top().second , val = q.top().first ; q.pop() ; for( int i = head[ u ] ; i ; i =e[ i ].nxt ) { int v = e[ i ].to ; if( d[ v ] == inf ) d[ v ] = val + 1 , q.push( make_pair( d[ v ] , v ) ) ; if( abs( d[ v ] - d[ u ] ) != 1 ) { puts( "No" ) ; return 0 ; } } } puts( "Yes" ) ; for( int i = 1 ; i <= n ; i ++ ) { printf( "%d\n" , d[ i ] ) ; } return 0 ;}
ARC063E

 16.[AtCoder ARC064D]An Ordinary Game

总结:结论题

结论题什么的最讨厌了!

这题结论要从奇偶性入手:

首先可以发现最后的字符串一定是形如“$ababab$”这样子由两个字符交替组成的

所以我们可以根据最后的这个字符串的奇偶性入手来判断谁赢谁输

如果字符串的第一个字符和最后一个字符相同,那么最后的字符串是奇数的,否则是偶数的

然后再根据原串的奇偶性就可以判断出答案了

神仙结论...

推不出来...

#include 
using namespace std ;#define N 100010char a[ N ] ;int main() { int cnt = 0 ; scanf( "%s" , a + 1 ) ; int n = strlen( a + 1 ) , k = n & 1 ; puts( abs( k - ( a[ n ] == a[ 1 ] ) ) & 1 ? "First" : "Second" ) ; }
ARC064D

 17.[AtCoder ARC098D]Xor Sum 2 

总结:two-pointers

$xor$ 性质: $a\ xor\ b <= a + b $ 

$a\ xor\ b == a + b$ 时当且仅当 a & b = 0 

所以$a_l\ xor ... xor\ a_r == a_l\ xor ... xor\ a_r$ 当且仅当a_l到a_r每一位上只有一个1 

于是这个区间是有单调性的(对于合法的$a_l $和 $a_r$ , $a_{l+1}$ 和 $a_{r}$显然也合法) 

所以用$two-pointers$搞搞就行了

#include 
using namespace std ;#define ll long long #define N 200010int n ;ll a[ N ] ;// xor 性质: a xor b <= a + b // a xor b == a + b 时当且仅当 a & b = 0 //所以a_l xor ... xor a_r == a_l xor ... xor a_r 当且仅当a_l到a_r每一位上只有一个1 //于是这个区间是有单调性的(对于合法的a_l 和 a_r , a_{l+1} 和 a_{r} 显然也合法) //所以用two-pointers搞搞 int main() { scanf( "%d" , &n ) ; for( int i = 1 ; i <= n ; i ++ ) { scanf( "%lld" , &a[ i ] ) ; } int l = 1 , r = 1 , now = 0 ; ll ans = 0 ; while( r <= n ) { while( ( now ^ a[ r ] ) == now + a[ r ] && r <= n ) now |= a[ r ++ ] , ans += r - l ; now ^= a[ l ++ ] ; } printf( "%lld\n" , ans ) ;}
ARC098D

 18.[AtCoder ARC066C]C - Lining Up

总结:结论题

这个atcoder怎么天天结论题

分类讨论一下

首先不管怎么样每个数都不能出现超过两次(左边一个右边一个然后就没了),否则就是不合法状态

对于长度为偶数的情况,显然0不会出现

对于长度为奇数的情况,显然0只会出现一次

所以把不合法状态判一下,答案就是$2^{n/2}$(每对数两种排列)

#include 
using namespace std ;const int N = 1e5 + 10 ;const int mod = 1e9+7 ;#define ll long longint n ;int a[ N ] ;int cnt[ N ] ;ll ans = 0 ;ll power( int a , int b ) { ll ans = 1 , base = a ; while( b ) { if( b & 1 ) ans = ans * base % mod ; base = base * base % mod ; b >>= 1 ; } return ans ;}int main() { scanf( "%d" , &n ) ; for( int i = 1 ; i <= n ; i ++ ) scanf( "%d" , &a[ i ] ) , cnt[ a[ i ] ] ++ ; for( int i = 0 ; i < n ; i ++ ) { if( cnt[ i ] > 2 ) return puts( "0" ) , 0 ; } if( n & 1 && cnt[ 0 ] != 1 ) return puts( "0" ) , 0 ; if( !( n & 1 ) && cnt[ 0 ] ) return puts( "0" ) , 0 ; printf( "%lld\n" , power( 2 , n / 2 ) ) ;}
ARC066C

 19.[AtCoder ARC070C]Go Home

总结:结论题

简单结论题

答案其实就是$\sum_{i=1}^{n}i>=x$的$n$的最小值

因为如果你跳出去了,跳出去的距离一定是之前跳过的

所以之前那一次不跳就可以了

#include 
using namespace std ;int x ;int main() { scanf( "%d" , &x ) ; int sum = 0 ; for( int i = 1 ; ; i ++ ) { sum += i ; if( sum >= x ) { printf( "%d\n" , i ) ; return 0 ; } }}
ARC070C

 20.[AtCoderARC071C]Dubious Document

总结:模拟

最后一题了..随便放个简单题吧

就是对于每个字母,它输出的次数就是它在所有字符串中出现的最小次数

开个map随便搞搞就行了

#include 
using namespace std ;int n ;map< char , int > cnt[ 100 ] ;map< int , int > ans ;char s[ 100 ][ 100 ] ;int main() { scanf( "%d" , &n ) ; for( int i = 1 ; i <= n ; i ++ ) { scanf( "%s" , s[ i ] + 1 ) ; for( int j = 1 , len = strlen( s[ i ] + 1 ) ; j <= len ; j ++ ) { cnt[ i ][ s[ i ][ j ] ] ++ ; } } for( int i = 0 ; i < 26 ; i ++ ) { int m = 1e10 ; for( int j = 1 ; j <= n ; j ++ ) { m = min( m , cnt[ j ][ (char)(i+'a') ] ) ; } ans[ i ] = m ; } for( int i = 0 ; i < 26 ; i ++ ) { for( int j = 1 ; j <= ans[ i ] ; j ++ ) { putchar( i + 'a' ) ; } } ans.clear() ; for( int i = 0 ; i < 26 ; i ++ ) { int m = 1e10 ; for( int j = 1 ; j <= n ; j ++ ) { m = min( m , cnt[ j ][ (char)( i + 'A' ) ] ) ; } ans[ i ] = m ; } for( int i = 0 ; i < 26 ; i ++ ) { for( int j = 1 ; j <= ans[ i ] ; j ++ ) { putchar( i + 'A' ) ; } } return 0 ;}
View Code

 

转载于:https://www.cnblogs.com/henry-1202/p/atcoder_training.html

你可能感兴趣的文章
需求获取常见的方法是进行客户访谈,结合你的实践谈谈会遇到什么问题,你是怎么解决的?...
查看>>
django之同源策略
查看>>
JAVA(时间对比排序程序)
查看>>
complex()
查看>>
各种字符串hash
查看>>
测试构造器它山之玉可以重构:身份证号(第四天)
查看>>
JS与PHP向函数传递可变参数的区别
查看>>
单元测试之初识
查看>>
golang github.com/go-sql-driver/mysql 遇到的数据库,设置库设计不合理的解决方法
查看>>
内存分配 保存数据
查看>>
嵌入式实时操作系统的可裁剪性及其实现
查看>>
VC++2012编程演练数据结构《31》狄杰斯特拉算法
查看>>
盘点:移动服务 #AzureChat
查看>>
Sass学习笔记
查看>>
C语言练习
查看>>
1001. Extending MyPoint class
查看>>
js使用showModalDialog,弹出一个自适应大小窗口
查看>>
[poj 3436]最大流+输出结果每条边流量
查看>>
webpack的安装
查看>>
字符流Reader和Writer
查看>>