HDOJ Monthly Contest – 2010.05.01

1004 Line belt

大意是有线段AB、CD,然后从A点到D点,AB、CD以及其他的位置上的速度依次为P、Q、R,然后问,最快的从A到达D的时间。

发现路径会有两个转折点,分别在AB、CD上,固定AB的其中一个位置为X,可以发现CD上的转折点呈单峰状曲线。同理另一边也是,于是可以这样做,AB上三分,确定X后,再到CD上三分,然后再回到AB上三分,这样来确定X、Y,直到稳定。从题目对精度的要求,这样的做法是可行的,而且发现最后的数据好像很仁道,1A。

  
    
1 #include < iostream >
2 #include < string >
3 #include < string .h >
4 #include < algorithm >
5 #include < vector >
6 #include < math.h >
7 #include < time.h >
8   using namespace std;
9
10   const double EPS = 1e - 6 ;
11
12   struct NODE
13 {
14 double x, y;
15 NODE( double xx, double yy) : x(xx), y(yy) {}
16 NODE() {}
17 }A, B, C, D, X, Y;
18
19   double P, Q, R;
20
21   double dis(NODE a, NODE b)
22 {
23 double x = a.x - b.x;
24 double y = a.y - b.y;
25 return sqrt(x * x + y * y);
26 }
27
28   double fun( double d, double v)
29 {
30 return d / v;
31 }
32
33   double go()
34 {
35 X = A;
36 Y = D;
37 while ( 1 )
38 {
39 // update X
40   NODE a = A, b = B;
41 while (fabs(b.x - a.x) > EPS || fabs(b.y - a.y) > EPS)
42 {
43 NODE mid = NODE((a.x + b.x) / 2 , (a.y + b.y) / 2 );
44 NODE mida = NODE((mid.x + b.x) / 2 , (mid.y + b.y) / 2 );
45 double fmid = fun(dis(A, mid), P) + fun(dis(mid, Y), R) + fun(dis(Y, D), Q);
46 double fmida = fun(dis(A, mida), P) + fun(dis(mida, Y), R) + fun(dis(Y, D), Q);
47 if (fmida >= fmid) b = mida;
48 else a = mid;
49 }
50 X = a;
51 // update Y
52   NODE c = C, d = D;
53 while (fabs(d.x - c.x) > EPS || fabs(d.y - c.y) > EPS)
54 {
55 NODE mid = NODE((c.x + d.x) / 2 , (c.y + d.y) / 2 );
56 NODE mida = NODE((mid.x + d.x) / 2 , (mid.y + d.y) / 2 );
57 double fmid = fun(dis(A, X), P) + fun(dis(X, mid), R) + fun(dis(mid, D), Q);
58 double fmida = fun(dis(A, X), P) + fun(dis(X, mida), R) + fun(dis(mida, D), Q);
59 if (fmida >= fmid) d = mida;
60 else c = mid;
61 }
62 if (fabs(c.x - Y.x) < EPS && fabs(c.y - Y.y) < EPS)
63 {
64 // printf("%lf %lf\n%lf %lf\n", X.x, X.y, Y.x, Y.y);
65 return fun(dis(A, X), P) + fun(dis(X, Y), R) + fun(dis(Y, D), Q);
66 }
67 Y = c;
68 }
69 }
70
71 int main()
72 {
73 int T;
74 scanf( " %d " , & T);
75 while (T -- )
76 {
77 scanf( " %lf%lf%lf%lf " , & A.x, & A.y, & B.x, & B.y);
78 scanf( " %lf%lf%lf%lf " , & C.x, & C.y, & D.x, & D.y);
79 scanf( " %lf%lf%lf " , & P, & Q, & R);
80 printf( " %.2lf\n " , go());
81 }
82 }

 

1008 Switch lights

在09年曹钦翔的论文里有,论文里提到翻硬币游戏1、2。1的话只能对行或者列上的两个硬币操作,而且要求坐标数比较大的那个硬币上向上的。可以将操作看成是从一个点移动到另一个点,由此可以推导出(x,y)处的SG值为X++Y,即X^Y。可以看成是有两堆的石子的NIM游戏,这个操作拆成这几个游戏,所以可以这样表示。而对于这道加强版,可以看成是3个操作的和,(a,b),(x,y)  (a<x,b<y) ,可以看成,(x,y)移动到(x,b),(x,y)移动到(a,y),(x,y)移动到(a,b)这三个操作的和,而对于这样的数定义为X**Y,x**y=mex{a**y++x**b++a**b}(0<=a<x,0<=b<y),然后根据数学推导能推倒出X**Y的一些性质来确定这个东西。以上X++Y是NIM和,X**Y是NIM积,根据论文的方法可以实现求X**Y,复杂度是log(x)*log(x)。对于这道题对于每个亮着灯的点,求SG值,然后再求下NIM和来判断就行。

  
    
1 #include < iostream >
2 #include < string >
3 #include < string .h >
4 #include < vector >
5 #include < math.h >
6 #include < map >
7 #include < time.h >
8 #include < algorithm >
9 using namespace std;
10
11 const int MAX = 1005 ;
12 const int INF = INT_MAX;
13
14 int pre[ 3 ][ 3 ] = { 0 , 0 , 0 , 0 , 1 , 2 , 0 , 2 , 3 };
15
16 int nimMultiPower( int x, int y)
17 {
18 //
19 if (x <= 1 ) return pre[x][y];
20 int i;
21 for (i = 0 ; ; i ++ )
22 {
23 if (x >= ( 1 << ( 1 << i)) && x < ( 1 << ( 1 << (i + 1 ))))
24 break ;
25 }
26 int M = ( 1 << ( 1 << i));
27 int P = x / M;
28 int S = y / M, T = y % M;
29
30 int d1 = nimMultiPower(P, S);
31 int d2 = nimMultiPower(P, T);
32
33 return (M * (d1 ^ d2)) ^ nimMultiPower(M / 2 , d1);
34 }
35
36 int nimMulti( int x, int y)
37 {
38 if (x < y) return nimMulti(y, x);
39 //
40 if (x <= 1 ) return pre[x][y];
41 int i;
42 for (i = 0 ; ; i ++ )
43 {
44 if (x >= ( 1 << ( 1 << i)) && x < ( 1 << ( 1 << (i + 1 ))))
45 break ;
46 }
47 int M = ( 1 << ( 1 << i));
48 int p = x / M, q = x % M;
49 int s = y / M, t = y % M;
50
51 int c1 = nimMulti(p, s);
52 int c2 = nimMulti(p, t) ^ nimMulti(q, s);
53 int c3 = nimMulti(q, t);
54
55 return ((c1 ^ c2) * M) ^ c3 ^ nimMultiPower(M / 2 , c1);
56 }
57
58 int main()
59 {
60 int T;
61 scanf( " %d " , & T);
62 while (T -- )
63 {
64 int n;
65 scanf( " %d " , & n);
66 int res = 0 ;
67 for ( int i = 0 ; i < n; i ++ )
68 {
69 int a, b;
70 scanf( " %d%d " , & a, & b);
71 res ^= nimMulti(a, b);
72 }
73 if (res == 0 ) printf( " Don't waste your time.\n " );
74 else printf( " Have a try, lxhgww.\n " );
75 }
76 }

 

1002 String

可以从换成路径的组合问题,从(0,0)到(m,n)不经过y=x-1这条线的路径数就是ans=C(m+n,m)-C(m+n,m-1),现在的问题是怎么求组合数的%20100501,这是个合数,m、n的级别是10^6的,对于(a/b)%m的问题,当b*m比较小的时候可以求a%(b*m)/b,但更多时候b*m是比较大的,而此时,当b和m互质的时候,可以去求b的逆元,然后a*b^-1%m就是答案,求逆元用扩展欧几里德,即解bx=1(mod m),但当b和m不互质的时候,问题就来了,下面链接里提供了这样一种做法,将m分解素因子,然后将a、b分解素因子,将和m素因子互质与不互质的因子分成两个集合。分解如下(a1*b1)/(a2*b2)%m,对于b2是和m互质的,所以b1/b2%m可以用刚才的方法做,对于a1/a2,因为b整除a,所以a1/a2是整数,直接p1^a1...pk^ak%m就行。这样的复杂度是log(m)+sqrt(a,b,m)。然后对这题,如果用这样的方法去做C(a,b)%m的话,感觉是可以就是比较卡时间,不过我最终还是无可奈何TLE了,郁闷。后来借助AC的代码,直接分解素因子,然后统计就行了,2*10^7的一张大表表示素数。最终的素数个数log(a)级别的,因为分解N!的素因子的复杂度是log(N)*α(N),α(x)表示小于x的素数的个数。所以时间还是蛮紧的,这样AC后600+ms,可想我之前的两次求C(m,n),感觉就很悬了,同时,后来Debug时又一次发生了件郁闷的事,就是差不多的代码就是会TLE,一个while里的条件写反了(囧),TLE的原因有时不是算法复杂度高,而是因为死循环了,教训呐。
感谢:

http://www.xayljq.com.cn/blog/read.php?19

  
    
1 #include < iostream >
2 #include < string >
3 #include < string .h >
4 #include < vector >
5 #include < math.h >
6 #include < map >
7 #include < set >
8 #include < time.h >
9 #include < algorithm >
10 using namespace std;
11
12 const int MAX = 2000005 ;
13 const int M = 20100501 ;
14
15 int tab[MAX];
16 int prime[MAX];
17 int num[MAX];
18 // 素数的个数
19 int top;
20
21 void ready()
22 {
23 top = 0 ;
24 memset(tab, 0 , sizeof (tab));
25 for ( int i = 2 ; i <= MAX; i ++ )
26 {
27 if (tab[i] == 0 )
28 {
29 prime[top ++ ] = i;
30 for ( int j = i + i; j < MAX; j += i)
31 tab[j] = 1 ;
32 }
33 }
34 }
35
36 __int64 fast( int a, int b)
37 {
38 __int64 t = a, res = 1 ;
39 while (b)
40 {
41 if (b & 1 ) res = res * t % M;
42 t = t * t % M;
43 b >>= 1 ;
44 }
45 return res;
46 }
47
48 void compute( int n, int sign)
49 {
50 if (n <= 1 ) return ;
51 for ( int i = 0 ; i < top && prime[i] <= n; i ++ )
52 {
53 int t = n;
54 while (t)
55 {
56 num[i] += sign * (t / prime[i]);
57 t /= prime[i];
58 }
59 }
60 }
61
62 __int64 go( int n, int m)
63 {
64 memset(num, 0 , sizeof (num));
65 __int64 res = 1 ;
66 int i;
67 int w = n - m + 1 ;
68 for (i = 0 ; i < top && prime[i] <= w; i ++ )
69 {
70 while (w % prime[i] == 0 )
71 {
72 num[i] ++ ;
73 w /= prime[i];
74 }
75 }
76 compute(m + n, 1 );
77 compute(m, - 1 );
78 compute(n + 1 , - 1 );
79 for (i = 0 ; i < top; i ++ )
80 {
81 if (num[i] == 0 ) continue ;
82 res = res * fast(prime[i], num[i]) % M;
83 if ( ! res ) return 0 ;
84 }
85 return res;
86 }
87
88 int main()
89 {
90 ready();
91 int T;
92 scanf( " %d " , & T);
93 while (T -- )
94 {
95 int n, m;
96 scanf( " %d%d " , & n, & m);
97 printf( " %I64d\n " , go(n, m));
98 }
99 }

 

1005 Trade
是道DP优化的题,之前不懂得单调队列优化DP,不过DP方程想的也有点不完善,状态很显然,然后去补了补单调队列,POJ上有道Sliding Window可以用单调队列优化,徐持衡《浅谈几类背包题》中有详细讲到单调队列,然后就把其中4个背包问题的前2个看了,后卡在第3个了,说可以在TreeDP上优化一类问题。不过终于深入了点了解01背包,多次背包和完全背包。多次背包可能就是常说的多重背包吧,可以用二进制把物品的数量K拆成log(k)那么多个,然后转换成01背包来解,加上个小优化(K是有上界的<=C/V),可以达到nC(logC)复杂度。上述背包问题均可以用滚动数组实现。用单调队列可以解决多次背包问题,O(nC)复杂度,POJ上有道Cash Machine可以拿来提交。对一类这样的DP,它在状态转移时dp[i][j]~max(dp[ii][j - k]),-a<=j-k<=a,可以用一个队列去维护这么个最大值或是最小值,其实模型就跟Sliding Window很像了,但一般应用到诸如这种明显的DP上时,为使在队列中维护这么个最值,需要注意的是要将插入队列中的值退化成某个值或是补充成某个值。这样才具有最值的维护性。某则当j变动时,队列中的值意义变动,而且花不起那个代价去更新队列。朱晨光的一本关于基本数据结构的应用中也有关于单调队列应用的例子,需要提到的是,里面关于栈应用的就是那道广告牌问题。回到多次背包问题,将状态j(表示体积)mod当前物品的体积V,按余数分成V份,对于每一份就可以用单调队列来优化转移了。
这道题的方程粗略如下:
dp[i][j]~max{dp[i - 1][j], dp[i - W - 1][j - k] - buy(k), dp[i - W - 1][j + k] + sell(k)}
注意初始化第一次可能交易的情况。

感谢:

http://hi.baidu.com/edwardmj/blog/item/82fcbcc49ecc4f179d163d21.html

  
    
1 #include < iostream >
2 #include < string >
3 #include < string .h >
4 #include < vector >
5 #include < math.h >
6 #include < map >
7 #include < set >
8 #include < time.h >
9 #include < algorithm >
10 using namespace std;
11
12 const int NINF = INT_MIN + 1e9;
13 const int MAX = 2e3 + 5 ;
14
15 int dp[MAX][MAX], bp[MAX], bc[MAX], sp[MAX], sc[MAX];
16 int T, MaxP, W;
17 int h, t;
18 int q[MAX][ 2 ];
19
20 void insert( int key, int value)
21 {
22 while (h < t && value >= q[t - 1 ][ 1 ]) t -- ;
23 q[t][ 0 ] = key, q[t][ 1 ] = value;
24 t ++ ;
25 }
26
27 void show()
28 {
29 for ( int i = 1 ; i <= T; i ++ )
30 {
31 for ( int j = 0 ; j <= MaxP; j ++ )
32 printf( " %d " , dp[i][j]);
33 printf( " \n " );
34 }
35 }
36
37 int go()
38 {
39 for ( int i = 1 ; i <= T; i ++ )
40 for ( int j = 0 ; j <= MaxP; j ++ )
41 dp[i][j] = NINF;
42 // 初始化,买进,第一笔交易
43 for ( int i = 1 ; i <= T; i ++ )
44 {
45 for ( int j = 0 ; j <= bc[i]; j ++ )
46 {
47 if (j == 0 ) dp[i][j] = 0 ;
48 else dp[i][j] = - bp[i] * j;
49 }
50 }
51 for ( int i = 2 ; i <= T; i ++ )
52 {
53 for ( int j = 0 ; j <= MaxP; j ++ ) dp[i][j] = max(dp[i][j], dp[i - 1 ][j]);
54 if (i - W - 1 <= 0 ) continue ;
55 int * a = dp[i - W - 1 ];
56 int * b = dp[i];
57
58 h = 0 , t = 0 ;
59 insert( 0 , a[ 0 ]);
60 for ( int j = 1 ; j <= MaxP; j ++ )
61 {
62 if (j - q[h][ 0 ] > bc[i]) h ++ ;
63 b[j] = max(b[j], q[h][ 1 ] - j * bp[i]);
64 insert(j, a[j] + bp[i] * j); // 退化成0股份,队列里存的已经是退化成0股份的值了
65 }
66 h = 0 , t = 0 ;
67
68 insert(MaxP, a[MaxP]);
69 for ( int j = MaxP - 1 ; j >= 0 ; j -- )
70 {
71 if (q[h][ 0 ] - j > sc[i]) h ++ ;
72 b[j] = max(b[j], q[h][ 1 ] + (MaxP - j) * sp[i]);
73 insert(j, a[j] - sp[i] * (MaxP - j)); // 补充成MaxP股份
74 }
75
76
77 }
78 int res = NINF;
79 for ( int i = 0 ; i <= MaxP; i ++ )
80 {
81 res = max(res, dp[T][i]);
82 }
83 return res;
84 }
85
86 int main()
87 {
88 int tcase;
89 scanf( " %d " , & tcase);
90 while (tcase -- )
91 {
92 scanf( " %d%d%d " , & T, & MaxP, & W);
93 for ( int i = 1 ; i <= T; i ++ ) scanf( " %d%d%d%d " , & bp[i], & sp[i], & bc[i], & sc[i]);
94 printf( " %d\n " , go());
95 }
96 }

 

1003 Roba's dream
这题比赛时没什么思路,强大的一道图论题。可以这样做,把1...n个数(身高)看成n个点,每个情侣看成一个点,情侣放在右边,n个点放在左边,每对情侣都与左边相应的身高连边,那么所求就是从左边的点集开始按顺序用类似求最大匹配的方法来找增光路,直到找不到位置。
这题的数据量不容许这样做,把n个身高看成n个点,把情侣看成无向边,那么就可以转换成,为相应的连续的点分配边,且分配的边不能重复,这里需要提到两个引理:
1.如果一个连通块是一棵树,那么有任意a-1个点可以分配到不重复的边。
2.如果一个连通块有环,那么a个点可以分配到不重复的边。
引理1可以用数学归纳法证明,引理2由引理1得证。
然后就可以用并查集实现,需要注意的是,最后枚举连续的数,若数所在集合是有环的话,那么肯定有解,若所在集合是颗树的话,那么到达该集合的最后一个点时就要退出了,这里实现的时候有个小技巧,就是在实现并查集的时候把父节点放在该集合的最后一个点处,同时最后在判断的时候用f[i]==i就可以了,因为此时关注的仅是孤立点或是集合中的最后一个点,对于集合中的点,即使不知道它是哪个集合的也无所谓,同时也不用路径压缩了,不过估计在join操作中已经压缩的差不多了。

感谢:
http://hi.baidu.com/edwardmj/blog/item/bdd0b333ad4f3215ebc4af59.html

  
    
1 #include < iostream >
2 #include < string >
3 #include < string .h >
4 #include < vector >
5 #include < math.h >
6 #include < map >
7 #include < set >
8 #include < time.h >
9 #include < algorithm >
10 using namespace std;
11
12 const int MAX = 5e6 + 5 ;
13
14 int f[MAX];
15 bool circle[MAX];
16 int n;
17
18 void ready()
19 {
20 memset(circle, false , sizeof (circle));
21 for ( int i = 0 ; i < MAX; i ++ ) f[i] = i;
22 }
23
24 int dfs( int x)
25 {
26 if (f[x] == x) return x;
27 f[x] = dfs(f[x]);
28 return f[x];
29 }
30
31 void join( int a, int b)
32 {
33 int aa = dfs(a);
34 int bb = dfs(b);
35 if (aa == bb)
36 {
37 circle[aa] = 1 ;
38 }
39 else if (aa > bb)
40 {
41 f[bb] = aa;
42 circle[aa] |= circle[bb];
43 }
44 else
45 {
46 f[aa] = bb;
47 circle[bb] |= circle[aa];
48 }
49 }
50
51 int go()
52 {
53 for ( int i = 0 ; i <= n; i ++ )
54 {
55 if (f[i] == i && ! circle[i]) return i;
56 }
57 }
58
59 int main()
60 {
61 int T;
62 scanf( " %d " , & T);
63 while (T -- )
64 {
65 scanf( " %d " , & n);
66 ready();
67 for ( int i = 0 ; i < n; i ++ )
68 {
69 double a, b;
70 scanf( " %lf%lf " , & a, & b);
71 int aa = a * 1000000 - 1500000 ;
72 int bb = b * 1000000 - 1500000 ;
73 join(aa, bb);
74 }
75 printf( " %d\n " , go());
76 }
77 }

 

 

 

你可能感兴趣的:(test)