Day 1 >>>
T1 >>
水题直接模拟AC;
考察三个知识点:1、你能不能编程
2、你会不会取模
3、你脑子抽不抽
然而第一次评测还是90,因为当模运算时 “ end == 0 ” 时需要将 end 改成 n;
代码:
#include <bits/stdc++.h> const int N = 1e5 + ; int n , k , x , y , end ; struct node {
int id , face ;
char nam [ ] ;
}
m [ N ] ; int main ( ) {
scanf ( "%d%d" , & n , & k ) ; for ( int i = ; i <= n ; i ++ ) {
scanf ( "%d%s" , & m [ i ] . face , m [ i ] . nam ) ;
m [ i ] . id = i ;
}
end = ;
for ( int i = ; i <= k ; i ++ ) {
scanf ( "%d%d" , & x , & y ) ;
if ( x == )
if ( m [ end ] . face == )
end = ( end + y + n ) % n ;
else
end = ( end - y + n ) % n ;
else
if ( m [ end ] . face == )
end = ( end - y + n ) % n ;
else
end = ( end + y + n ) % n ;
if ( end == ) end = n ;
}
printf ( "%s" , m [ end ] . nam ) ;
return ;
}
Day1 T1
T2 >>
这一年,最难的题目,没有之一!(游戏体验极差!)
25 分,直接连剖+暴力:
连剖求出 LCA(s,t),之后求出 s -> t 的路长,从 s 和 t 一个一个暴力往上跳,边跳边检查,符合 w [ i ] == tim , 则 ans ++;
80 分
主席树什么的,当时是 YYH 讲的 。(顺便吐槽一下,他讲的方法是 O(nm*logn),然而他的代码是 O(mlogn)的 …… 鬼知道他哪里抄的 …… )
100 分
桶+链剖+dfs
代码:
#include <bits/stdc++.h> const int N = + ; int head [ N << ] , to [ N << ] , nxt [ N << ] , cn ;
int heads [ N << ] , tos [ N << ] , nxts [ N << ] , cn_s ;
int headt [ N << ] , tot [ N << ] , nxtt [ N << ] , cn_t ;
int headl [ N << ] , tol [ N << ] , nxtl [ N << ] , cn_l ;
int siz [ N ] , son [ N ] , dep [ N ] , top [ N ] , fa [ N ] ;
int ans [ N ] , delta [ N ] , tong1 [ N ] , tong2 [ N ] , w [ N ] ;
int n , m , x , y , st , en ; struct Trie {
int s , t , len , lca ;
}
dot [ N << ] ; struct Builder {
void create ( int u , int v ) {
cn ++ ;
to [ cn ] = v ;
nxt [ cn ] = head [ u ] ;
head [ u ] = cn ;
}
void create_s ( int u , int v ) {
cn_s ++ ;
tos [ cn_s ] = v ;
nxts [ cn_s ] = heads [ u ] ;
heads [ u ] = cn_s ;
}
void create_t ( int u , int v ) {
cn_t ++ ;
tot [ cn_t ] = v ;
nxtt [ cn_t ] = headt [ u ] ;
headt [ u ] = cn_t ;
}
void create_l ( int u , int v ) {
cn_l ++ ;
tol [ cn_l ] = v ;
nxtl [ cn_l ] = headl [ u ] ;
headl [ u ] = cn_l ;
}
}
build ; void dfs1 ( int u , int f ) {
int v ;
siz [ u ] = ;
for ( int i = head [ u ] ; i ; i = nxt [ i ] ) {
v = to [ i ] ;
if ( v == f ) continue ;
fa [ v ] = u ;
dep [ v ] = dep [ u ] + ;
dfs1 ( v , u ) ;
siz [ u ] += siz [ v ] ;
if ( siz [ v ] > siz [ son [ u ] ] )
son [ u ] = v ;
}
} void dfs2 ( int u , int tp ) {
int v ;
top [ u ] = tp ;
if ( son [ u ] ) dfs2 ( son [ u ] , tp ) ;
for ( int i = head [ u ] ; i ; i = nxt [ i ] ) {
v = to [ i ] ;
if ( v == fa [ u ] || v == son [ u ] ) continue ;
dfs2 ( v , v ) ;
}
} int LCA ( int u , int v ) {
while ( top [ u ] != top [ v ] ) {
if ( dep [ top [ u ] ] < dep [ top [ v ] ] ) u ^= v ^= u ^= v ;
u = fa [ top [ u ] ] ;
}
return dep [ u ] < dep [ v ] ? u : v ;
} void DFS ( int u , int f ) {
int v ;
delta [ u ] = tong1 [ dep [ u ] + w [ u ] ] + tong2 [ dep [ u ] - w [ u ] + N ] ;
for ( int i = head [ u ] ; i ; i = nxt [ i ] ) {
v = to [ i ] ;
if ( v == f ) continue ;
DFS ( v , u ) ;
}
for ( int i = heads [ u ] ; i ; i = nxts [ i ] ) tong1 [ dep [ u ] ] ++ ;
for ( int i = headt [ u ] ; i ; i = nxtt [ i ] ) tong2 [ dep [ u ] - dot [ tot [ i ] ] . len + N ] ++ ;
ans [ u ] = tong1 [ dep [ u ] + w [ u ] ] + tong2 [ dep [ u ] - w [ u ] + N ] ;
for ( int i = headl [ u ] ; i ; i = nxtl [ i ] ) {
tong1 [ dep [ dot [ tol [ i ] ] . s ] ] -- ;
tong2 [ dep [ dot [ tol [ i ] ] . t ] - dot [ tol [ i ] ] . len + N ] -- ;
}
} int main ( ) { scanf ( "%d%d" , & n , & m ) ;
for ( int i = ; i < n ; i ++ ) {
scanf ( "%d%d" , & x , & y ) ;
build . create ( x , y ) ;
build . create ( y , x ) ;
}
for ( int i = ; i <= n ; i ++ )
scanf ( "%d" , & w [ i ] ) ;
dfs1 ( , - ) ;
dfs2 ( , - ) ;
for ( int i = ; i <= m ; i ++ ) {
scanf ( "%d%d" , & st , & en ) ;
dot [ i ] . s = st ; dot [ i ] . t = en ;
dot [ i ] . lca = LCA ( st , en ) ;
dot [ i ] . len = dep [ st ] + dep [ en ] - dep [ dot [ i ] . lca ] * ;
}
for ( int i = ; i <= m ; i ++ )
build . create_s ( dot [ i ] . s , i ) ;
for ( int i = ; i <= m ; i ++ )
build . create_t ( dot [ i ] . t , i ) ;
for ( int i = ; i <= m ; i ++ )
build . create_l ( dot [ i ] . lca , i ) ;
DFS ( , - ) ;
for ( int i = ; i <= m ; i ++ )
if ( dep [ dot [ i ] . lca ] + w [ dot [ i ] . lca ] == dep [ dot [ i ] . s ] )
ans [ dot [ i ] . lca ] -- ;
for ( int i = ; i <= n ; i ++ )
printf ( "%d " , ans [ i ] - delta [ i ] ) ;
return ;
}
Day1 T2
T3 >>
另址:https://www.luogu.org/blog/horsepower2001/solution-p1850
第一眼:期望dp,开始琢磨。结果意识到写不来!
最后打了暴力,处理了m=0的情况……
正解:
由于数据范围十分友好,所以我们可以%一下弗洛伊德!——预处理出换教室的最小费用,从而部分地使得最后的期望值最小;
最短路已经跑出来了,那么为何dp呢?
我们知道对于第i节课申请抉择的期望值,是可以被前一个i-1个课的申请抉择所影响的,那么最后的答案也是被倒数第二个节课的答案所影响的……所以要使得最后的决策最优,那那么转化为子问题最优,当然dp。
首先得到了一个简易的方程:
dp [ i ] = dp [ i - 1 ] + ??? - ???;
发现方程十分难递推,因为没有控制决策的个数,不知道决策用完了没有……对了!再加一维:
dp [ i ] [ j ] = dp [ i - 1 ] [ j - 1 ] + ??? - ???;(表示第i节课时已经用了j个决策时最优的期望值)
还是很难推不是吗?有两种情况呢,上一个状态到底是哪一个状态来的呢?到底是决策了还是没有决策呢?
忽然意识到换教室还是不换教室是非此即彼的,不管概率多少,所以——新增一个状态:
dp [ i ] [ j ] [ 1 or 0 ] ;
第三维的 1 表示当前第 i 节课的申请书申请了,0 则反之;
有两种情况:
1 、当前第 i 节课没有提交了申请书,那么第 i 节课就一定没有换教室,那么终点一定在 c [ i ];但是有没有可能上一节课换了呢?是有可能的,所以又有两种情况:
(1)如果上节课提交了申请书,那么有可能过,也有可能不过,所以得到一个递推:
当前 dp [ i ] [ j ] [ 0 ] = dp [ i - 1 ] [ j ] [ 1 ] + p [ i - 1 ] * c [ dx [ i - 1 ] ] [ cx [ i ] ] + ( 1 - p [ i - 1 ] ) * c [ cx [ i - 1 ] ] [ cx [ i ] ] ;
(2)如果上节课没有提交申请书,那么就只能乖乖从 c [ i - 1 ] -> c [ i ]!得到另一个递推:
当前 dp [ i ] [ j ] [ 0 ] = dp [ i - 1 ] [ j ] [ 0 ] + c [ cx [ i - 1 ] ] [ cx [ i ] ] ;
2 、当前第 i 节课的确提交了申请书,那么就将有 2 种情况:
(1):第 i 的上节课提交了申请书:
【1】前一节课通过,当前课通过,加上期望:p [ i - 1 ] * p [ i ] * c [ dx [ i - 1 ] ] [ dx [ i ] ];
【2】前一节课通过,当前课未通过,加上期望:p [ i - 1 ] * ( 1 - p [ i ] ) * c [ dx [ i - 1 ] ] [ cx [ i ] ];
【3】前一节课未通过,当前课通过,加上期望:( 1 - p [ i - 1 ] ) * p [ i ] * c [ cx [ i - 1 ] ] [ dx [ i ] ];
【4】前一节课未通过,当前课未通过,加上期望:( 1 - p [ i - 1 ] ) * ( 1 - p [ i ] ) * c [ cx [ i - 1 ] ] [ cx [ i ] ];
(2):第 i 的上节课未提交申请书:那么前一节课肯定只有乖乖呆在 c [ i - 1 ],所以只有两种情况:
【1】当前通过,那么加上期望:p [ i ] * c [ cx [ i - 1 ] ] [ dx [ i ] ];
【2】当前未通过,那么加上期望:( 1 - p [ i ] ) * c [ cx [ i - 1 ] ] [ cx [ i ] ];
于是得到总的 dp 方程,喜欢看哪一个看哪一个:)
dp [ i ] [ j ] [ ] = minx ( dp [ i - ] [ j ] [ ] + c [ cx [ i - ] ] [ cx [ i ] ] , dp [ i - ] [ j ] [ ] + p [ i - ] * c [ dx [ i - ] ] [ cx [ i ] ] + ( - p [ i - ] ) * c [ cx [ i - ] ] [ cx [ i ] ] ) ; dp [ i ] [ j ] [ ] = minx ( dp [ i - ] [ j - ] [ ] + p [ i ] * c [ cx [ i - ] ] [ dx [ i ] ] + ( - p [ i ] ) * c [ cx [ i - ] ] [ cx [ i ] ] , dp [ i - ] [ j - ] [ ] + ( - p [ i - ] ) * ( - p [ i ] ) * c [ cx [ i - ] ] [ cx [ i ] ] + ( - p [ i - ] ) * p [ i ] * c [ cx [ i - ] ] [ dx [ i ] ] + p [ i - ] * ( - p [ i ] ) * c [ dx [ i - ] ] [ cx [ i ] ] + p [ i - ] * p [ i ] * c [ dx [ i - ] ] [ dx [ i ] ] ) ;
Dp Function
dp [ i ] [ j ] [ 0 ] = minx (
dp [ i - 1 ] [ j ] [ 0 ] + c [ cx [ i - 1 ] ] [ cx [ i ] ] ,
dp [ i - 1 ] [ j ] [ 1 ] + p [ i - 1 ] * c [ dx [ i - 1 ] ] [ cx [ i ] ] +
( 1 - p [ i - 1 ] ) * c [ cx [ i - 1 ] ] [ cx [ i ] ]
) ;
dp [ i ] [ j ] [ 1 ] = minx (
dp [ i - 1 ] [ j - 1 ] [ 0 ] +
p [ i ] * c [ cx [ i - 1 ] ] [ dx [ i ] ] +
( 1 - p [ i ] ) * c [ cx [ i - 1 ] ] [ cx [ i ] ] ,
dp [ i - 1 ] [ j - 1 ] [ 1 ] +
( 1 - p [ i - 1 ] ) * ( 1 - p [ i ] ) * c [ cx [ i - 1 ] ] [ cx [ i ] ] +
( 1 - p [ i - 1 ] ) * p [ i ] * c [ cx [ i - 1 ] ] [ dx [ i ] ] +
p [ i - 1 ] * ( 1 - p [ i ] ) * c [ dx [ i - 1 ] ] [ cx [ i ] ] +
p [ i - 1 ] * p [ i ] * c [ dx [ i - 1 ] ] [ dx [ i ] ]
) ;
请务必仔细研究上述方程!!!虽然冗长,但是一旦理解就很容易写出来(我这种蒟蒻都能干的事)!
代码:
#include <bits/stdc++.h> std :: queue < int > q ; const int N = + ;
const double inf = 1e9 * 1.0 + ; int head [ N << ] , nxt [ N << ] , dis [ N << ] , to [ N << ] ;
double p [ N * ] , dp [ N ] [ N ] [ ] ;
int cx [ N ] , dx [ N ] , c [ N ] [ N ] ;
int n , m , vv , ee , cn , x , y , w ;
bool vis [ N ] ; double minx ( double a , double b ) {
return a > b ? b : a ;
} int minxint ( int a , int b ) {
return a > b ? b : a ;
} int main ( ) {
//freopen ( "classroom.in" , "r" , stdin ) ;
//freopen ( "classroom.out" , "w" , stdout ) ;
scanf ( "%d%d%d%d" , & n , & m , & vv , & ee ) ; for ( int i = ; i <= n ; i ++ )
scanf ( "%d" , & cx [ i ] ) ;
for ( int i = ; i <= n ; i ++ )
scanf ( "%d" , & dx [ i ] ) ; for ( int i = ; i <= n ; i ++ )
scanf ( "%lf" , & p [ i ] ) ; for ( int i = ; i <= vv ; i ++ )
for ( int j = ; j <= vv ; j ++ )
c [ i ] [ j ] = c [ j ][ i ] = inf ; for ( int i = ; i <= ee ; i ++ ) {
scanf ( "%d%d%d" , & x , & y , & w ) ;
if( x != y ) {
c [ x ] [ y ] = c [ y ] [ x ] = minxint ( c [ x ] [ y ] , w ) ;
}
} for ( int i = ; i <= vv ; i ++ ) c [ i ] [ i ] = ; for ( int k = ; k <= vv ; k ++ )
for ( int i = ; i <= vv ; i ++ )
for ( int j = ; j <= vv ; j ++ )
c [ i ] [ j ] = minxint ( c [ i ] [ j ] , c [ i ] [ k ] + c [ k ] [ j ] ) ; for ( int i = ; i <= n ; i ++ )
for ( int j = ; j <= m ; j ++ )
dp [ i ] [ j ] [ ] = dp [ i ] [ j ] [ ] = inf ;
dp [ ] [ ] [ ] = ; dp [ ] [ ] [ ] = ;
for ( int i = ; i <= n ; i ++ ) {
for ( int j = ; j <= m ; j ++ ) { dp [ i ] [ j ] [ ] = minx ( dp [ i - ] [ j ] [ ] + c [ cx [ i - ] ] [ cx [ i ] ]
,
dp [ i - ] [ j ] [ ] + p [ i - ] * c [ dx [ i - ] ] [ cx [ i ] ]
+ ( - p [ i - ] ) * c [ cx [ i - ] ] [ cx [ i ] ] ) ;
if ( j ) {
dp [ i ] [ j ] [ ] = minx ( dp [ i - ] [ j - ] [ ] + p [ i ] * c [ cx [ i - ] ] [ dx [ i ] ]
+ ( - p [ i ] ) * c [ cx [ i - ] ] [ cx [ i ] ]
,
dp [ i - ] [ j - ] [ ]
+ ( - p [ i - ] ) * ( - p [ i ] ) * c [ cx [ i - ] ] [ cx [ i ] ]
+ ( - p [ i - ] ) * p [ i ] * c [ cx [ i - ] ] [ dx [ i ] ]
+ p [ i - ] * ( - p [ i ] ) * c [ dx [ i - ] ] [ cx [ i ] ]
+ p [ i - ] * p [ i ] * c [ dx [ i - ] ] [ dx [ i ] ] ) ;
}
}
}
double ans = inf ;
for ( int i = ; i <= m ; i ++ )
ans = minx ( ans , minx ( dp [ n ] [ i ] [ ] , dp [ n ] [ i ] [ ] ) ) ;
printf ( "%.2lf" , ans ) ;
return ;
}
Day1 T3
这代码可能只有复制到Dev上看了,后摇厉害……
Day2 >>>
T1 >>
又是一道水垮垮的题,很容易想到杨辉三角……然而有坑!
很容易将答案记成 ans [ i ] [ j ] 是本行到第 j 个元素以及前面所有的值的贡献,也就是覆盖了:
(略显模糊,编辑原因)
应该维护纵向前缀和,最后for(1->m)将所有该行的 j 列的前缀和加起来!
代码:
#include <bits/stdc++.h> const int N = + ; long long yh [ N ] [ N ] , ans [ N ] [ N ] , k ;
int n , m , t ; void predo ( ) {
for ( int i = ; i <= ; i ++ )
for ( int j = ; j <= i ; j ++ )
if ( j == || j == i )
yh [ i ] [ j ] = % k ;
else
yh [ i ] [ j ] = ( yh [ i - ] [ j ] % k + yh [ i - ] [ j - ] % k ) % k ;
} void get ( ) {
long long cnt = ;
for ( int i = ; i <= ; i ++ )
for ( int j = ; j <= i ; j ++ )
if ( yh [ i ] [ j ] == )
ans [ i ] [ j ] = ans [ i - ] [ j ] + ;
else
ans [ i ] [ j ] = ans [ i - ] [ j ] ;
} int main ( ) {
//freopen ( "problem.in" , "r" , stdin ) ;
//freopen ( "problem.out" , "w" , stdout ) ;
scanf ( "%d%lld" , & t , & k ) ;
predo ( ) ;
get ( ) ;
for ( int i = ; i <= t ; i ++ ) {
long long tot = ;
scanf ( "%d%d" , & n , & m ) ;
if ( n < m ) m = n ;
for ( int i = ; i <= m ; i ++ )
tot += ans [ n ] [ i ] ;
printf ( "%lld\n" , tot ) ;
}
return ;
}
Day2 T1
若不是那个小细节,我就可以吊打 YL 同学和 Gwyndolin 了!
T2 >>
虫虫的真有趣啊!
Limon上的暴力55,Cena是45:优先队列,但只能处理 q=0 的情况,每次弹出队首,按照 i % t==0 输出解;然后写一个 O(n*m+m+n)的暴力,过两组 n,m 数据规模较小的数据,55。
90分或100分:
三个“单调队列”。为什么要打引号呢,因为它完全不需要维护,只是里面的元素是单调递减的。
用法:
单调队列 1:que [ 1 ],作用是存一开始所有的蚯蚓长度,最大值为队首。
单调队列 2:que [ 2 ],作用是存每次切断后的两个蚯蚓其中一个蚯蚓的长度,每次直接入队,最大值为队首。
单调队列 3:que [ 3 ],作用是存每次切断后的两个蚯蚓中另一个蚯蚓的长度,每次直接入队,最大值为队首。
但为什么要这样存呢?
我们不如先来看下只用一个队列会怎么样:
先排序,使队列单调递减,这之后,取出第一个(最长的那只蚯蚓),截断。
那么问题来了,这截断的两只蚯蚓如何进队呢?
无法判断蚯蚓的唯一去处,因为被截断得蚯蚓如果要保证下一次队首一定是最大的话,进队之后必须使得队列有序,而判断位置就是 O(n)的,总共就是 O(nm),爆了。
那么三个队列为什么可以直接入队、取队首呢?
证明:对于两次切断的蚯蚓 X、Y 来说,假设 X 蚯蚓相对 Y 先一个切,Y 蚯蚓相对 X 后一个切,那么 X 的长度一定大于 Y 。X 切断后的两只蚯蚓设为 L1、R1,Y 切断后的两只蚯蚓设为 L2、R2。
那么当 X 切断的时,X 将分成 L1、R1,此时 Y 将增加 q;当下一次 Y 切的时候,L1 生长成 L1 + q,R1 生长成 R1 + q,但是此时 Y 切成的 L2、R2并不生长。
此时有队列:
que [ 1 ]:L1 + q,L2 + q * u / v;
que [ 1 ]:R1 + q,R2 + q *(1 - u / v);
由于 L1 来自 X,比例为 phi;L2 来自 Y,比例也为 phi;那么由于 X > Y,所以 L1 > L2,又因为 u / v < 1,那么 L1 + q > L2 + q * u / v。
所以队列 1 一定是单调的!
同理,队列 2 也是单调的!
所以每一次取出三个队列的队首,取最大值,切断,原队列出队,两段进入 que [ 2 ] 和 que [ 3 ]。
证明了单调性,然而如何解决增长的问题呢?
可以维护相对值。不难发现,如果有一些蚯蚓幸免了神刀手,那么它一定增长了 q * time,time 表示的是过了几秒;而那些未能幸免的蚯蚓,一定少了若干次。
所以每次且的时候,既要保证被切断的蚯蚓长度是正确的,又要保证切断后的蚯蚓与其他的蚯蚓长度的相对值不变,步骤如下:
delta:记录幸免了神刀手的蚯蚓应该生长的长度。
maxx:表示的是当前蚯蚓最长的长度。
步骤1、maxx += delta;
步骤2、delta += q;
步骤3、切断,得到 L1,R1;
步骤4、L1 -= delta,R1 -= delta;
这样保证了蚯蚓的相对长度差值不变;最后所有的蚯蚓实际的长度=队列中所有的元素+delta。
代码:
#include <bits/stdc++.h> const int N = + ; int que [ ] [ N ] , head [ ] , tail [ ] , delta ;
int n , q , t , m ;
double u , v , p ; bool cmp ( const int a , const int b ) {
return a > b ;
} int read ( ) {
int f=,p=;char c=getchar();
while(c>''||c<''){if(c=='-')f=-;c=getchar();}
while(c>=''&&c<=''){p=p*+c-'';c=getchar();}
return f*p;
} int main ( ) {
scanf ( "%d%d%d%d%d%d" , & n , & m , & q , & u , & v , & t ) ;
p = u / v ;
for ( int i = ; i <= n ; i ++ )
que [ ] [ i ] = read ( ) ;
std :: sort ( que [ ] + , que [ ] + n + , cmp ) ;
head [ ] = head [ ] = head [ ] = ;
tail [ ] = tail [ ] = ;
tail [ ] = n + ;
for ( int i = ; i <= m ; i ++ ) {
int maxx = - 0x7fffffff , place = ;
for ( int j = ; j <= ; j ++ )
if ( que [ j ] [ head [ j ] ] > maxx && tail [ j ] > head [ j ] )
place = j , maxx = que [ j ] [ head [ j ] ] ;
head [ place ] ++ ;
maxx += delta ;
delta += q ;
int worm1 = floor ( maxx * p ) - delta ;
int worm2 = maxx - floor ( maxx * p ) - delta ;
que [ ] [ tail [ ] ++ ] = worm1 ;
que [ ] [ tail [ ] ++ ] = worm2 ;
if ( i % t == ) printf ( "%d " , maxx ) ;
}
printf ( "\n" ) ;
for ( int i = ; i <= n + m ; i ++ ) {
int maxx = - 0x7fffffff , place = ;
for ( int j = ; j <= ; j ++ )
if ( que [ j ] [ head [ j ] ] > maxx && tail [ j ] > head [ j ] )
place = j , maxx = que [ j ] [ head [ j ] ] ;
head [ place ] ++ ;
maxx += delta ;
if ( i % t == ) printf ( "%d " , maxx ) ;
}
return ;
}
Day2 T2
顺便提一句,不知道为什么会被Cena卡常了……
T3 >>
做过的题,复制自己之前写的题解。
原网址:
没有人知道我A掉这道题时
是多么激动和沮丧
激动是因为终于自己一次性A了第一道noip2016的题
沮丧是因为我竟然将半个小时的时间近乎崩溃的寻找状态压缩的方程错误,而问题居然是出在double精度处理上(F**k U double)!!!
先讲一讲思路
由题意得
条件1、猪的个数小于18个。
条件2、鸟的个数无穷大。
条件3、鸟必须从(0,0)打出,并且抛物线a*x^2+b*x的a必须严格小于0;
问题1、我们需要求得最少用打完所有猪的鸟是多少只。
若要求打完所有猪猪需要用的鸟的个数,我们需要用打完某一部分猪猪后所用的鸟的最小值去更新打完所有猪猪所用的鸟的最小数量,于是我们很容易将这个问题转化为多个子问题的最优解,可以理解吗?
那么显然这是一道动态规划的题。
但是接下来如何转移方程呢?
这个难倒了我们。
难点1、
由于每一条经过某两个猪猪可以确定一条抛物线,但其所经过的其他猪猪的数量是不知道的。
脑洞大开——
不对,既然猪猪的数量这么少,我们可否将猪猪被打死了或者没有被打死构成一个0101010……的长度为n的状态,进行状压。
定义状态
在“状态01串”上的在i位置上的“1”表示当前状态按照输入顺序的第i只猪猪已经被打死了;
在“状态01串”上的在i位置上的“0”表示当前状态按照输入顺序的第i只猪猪仍未被打死;
每一个状态是什么情况,哪些猪猪已经被打死了是没有被打死一目了然,于是我们可以去枚举状态。
定义dp数组
dp[ i ]为一维数组;
下标 i 表示打小鸟打到 i 这个状态所使用的鸟的最小值是dp[ i ]。
解决难点1
我们可以对每两个猪猪确定的一条抛物线解析式的a和b计算出来。接着枚举所有的猪猪,如果可以被打的话也加入到打掉这两个猪猪所牵连的其他猪猪的集合之中!存入数组g[ id1 ][ id2 ],表示打死编号为id1以及id2的猪猪的抛物线一起打死的(被牵连的)猪猪状态。
注意,这个状态无论上一层是什么状态,只要使用了这条抛物线,对于被牵连的猪猪的状态一定是1!
我们怎么对这个状态打新的并且还没有死的猪猪呢?
这里用到了位运算符:| (或)。
优点
1|1=1
1|0=1
0|1=1
0|0=0
无论id1与1d2两头猪猪所确定的抛物线所经过的每一头猪猪有没有被打死,总之没有被打死的(“0”)“或”上加上抛物线不会被打死的(“0”),还是不会被打死。其他情况根据上面的等式推导,一定成立,可以感性理解或者推一下,不再赘述。
得解
所以可以枚举代表当前状态01串的十进制数,被打的第一头将被打死的目标傻猪猪id1和第二头将被一起打死的目标傻猪猪id2;
在当前状态打一只小鸟:dp[ s | g[ i ][ j ] ];
加上费用,每次为+1;
注意,但有可能这条抛物线对当前状态一点贡献都没有,也就是其经过的所有猪猪都早就被更优的方案:dp[ s | g[ i ][ j ] ]打死了。那我干脆就不打了,所以要两者取min值。
得到状态转移方程:
dp[ s ] = min( dp[ s | g[ i ][ j ] ] , dp[ s ]+1 );
还有一点小瑕疵
对于double来说,求出某一条抛物线的我们,判断其他猪猪是否会被该抛物线射到时,一定不能单纯用“==”去判断ax^2+bx是否与y相等,而是要用ax^2+bx-y<1e-6!天哪,就是这个,我排了半个小时的dp雷,结果一无所获。上了个厕所才发现,我的天,是不是精度的问题!!!
改了就AC了,真是神奇的double。
以后一定要注意啊!!!
贴上代码
比较丑,别嫌弃……
而且被某胡姓老师给带坏了定义变量是时变量的名字 —— 全用中文拼音……
代码:
#include<bits/stdc++.h> const int N=+; using namespace std; int dp[<<],g[][],n,m,T;
double x[N],y[N],a,b; int yu_chu_li(double aa,double bb,double cc,double aaa,double bbb,double ccc){
int er_jin_zhi=;
b=(cc-ccc*aa/aaa)/(bb-bbb*aa/aaa);
a=(cc-ccc*bb/bbb)/(aa-aaa*bb/bbb);
if(a>= || aa==aaa ) return ;
for(register int k=;k<=n;k++)
if(abs(x[k]*x[k]*a+x[k]*b-y[k])<1e-)
er_jin_zhi+=(<<(k-));
return er_jin_zhi;
} int main(){
scanf("%d",&T);
for(int t=;t<=T;t++){
memset(dp,0x7f,sizeof(dp));
memset(g,,sizeof(g));
scanf("%d%d",&n,&m);
for(register int i=;i<=n;i++)
scanf("%lf%lf",&x[i],&y[i]);
for(register int i=;i<=n;i++)
for(register int j=;j<=n;j++)
g[i][j]=yu_chu_li(x[i]*x[i],x[i],y[i],x[j]*x[j],x[j],y[j]);
dp[]=;
for(register int i=;i<=n;i++)
g[i][i]=(<<(i-));
for(register int i=;i<=n;i++)
for(register int j=;j<=n;j++)
for(register int s=;s<(<<n);s++)
if(dp[s|g[i][j]]>dp[s]+)
dp[s|g[i][j]]=dp[s]+;
printf("%d\n",dp[(<<n)-]);
} return ;
}
Day2 T3
考场上写的被卡了三个点 WA,不知道为什么……
注意时间不剪枝复杂度:O(2^n * n^2)= 84934656 = 8 * 10^7 > 5 * 10 ^ 7 洛谷没卡我但 Cena 评测机卡了,神TM……
这就是 2016 的 Noip;