2015 沈阳赛区网络赛
hdu 5456 Matches Puzzle Game
从低位到高位dp,每次枚举 B,C,维护借位,还有B,C是否到达最高位。
dp(剩余火柴数,是否需要借位,B是否到达最高位,C是否到达最高位)
const int N = 500;
const int num[10] = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6};
LL dp[N+5][2][2][2], Mod;
inline void add_mod(LL& x, LL y) {
x += y; if ( x >= Mod ) x -= Mod;
}
LL go(int n, int carry, int b, int c) {
if ( n == 0 ) {
if ( carry ) return 0;
if ( !b || !c ) return 0;
return 1;
}
LL& ret = dp[n][carry][b][c];
if ( ret != -1 ) return ret;
ret = 0;
LL sum;
int a;
if ( b ) {
if ( c ) {
if ( carry )
add_mod(ret, go(n - num[carry], 0, 1, 1));
else
return 0;
} else {
for (int i = 0; i <= 9; ++ i) {
a = i + carry;
sum = num[i] + num[ a % 10 ];
if ( sum <= n ) {
add_mod(ret, go(n - sum, a / 10, 1, 0));
if ( i != 0 )
add_mod(ret, go(n - sum, a / 10, 1, 1));
}
}
}
} else {
if ( c ) {
for (int i = 0; i <= 9; ++ i) {
a = i + carry;
sum = num[i] + num[ a % 10 ];
if ( sum <= n ) {
add_mod(ret, go(n - sum, a / 10, 0, 1));
if ( i != 0 )
add_mod(ret, go(n - sum, a / 10, 1, 1));
}
}
} else {
for (int i = 0; i <= 9; ++ i)
for (int j = 0; j <= 9; ++ j) {
a = i + j + carry;
sum = num[i] + num[j] + num[ a % 10 ];
if ( sum <= n ) {
add_mod(ret, go(n - sum, a / 10, 0, 0));
if ( i != 0 )
add_mod(ret, go(n - sum, a / 10, 1, 0));
if ( j != 0 )
add_mod(ret, go(n - sum, a / 10, 0, 1));
if ( i != 0 && j != 0 )
add_mod(ret, go(n - sum, a / 10, 1, 1));
}
}
}
}
return ret;
}
FOJ有奖月赛-2015年10月
G
http://acm.fzu.edu.cn/problem.php?pid=2204
一行上有n个球,可以染成黑或白,要求连续7个不能是同样颜色,求方案数
(开始的颜色,前i个球,最后的段长为j,最后一段的颜色)
然后,枚举最开始放黑或白,进行dp预处理
对
n>=7
枚举第一段的长度,最后一段的长度,分情况讨论,累加答案。
优化:因为第一个位置放黑白是对称的,所以可以让第一个位置固定为白色,然后得到的答案乘2
int dp[N+5][10][2];
void add_mod(int& x, int y) {
x += y; if ( x >= Mod ) x -= Mod;
}
void init(int n) {
memset(dp, 0, sizeof(dp));
dp[1][1][0] = 1;
for(int i = 1; i < n; ++ i)
for(int j = 1; j <= 6; ++ j)
if ( j <= i )
for (int k = 0; k <= 1; ++ k)
if ( dp[i][j][k] ) {
add_mod(dp[i+1][1][!k], dp[i][j][k]);
add_mod(dp[i+1][j+1][k], dp[i][j][k]);
}
}
int main() {
init(N);
int t, cas = 0;
scanf("%d", &t);
while ( t -- ) {
int n;
scanf("%d", &n);
int ans = 0;
if ( n < 7 ) {
ans = (1 << n);
} else {
for (int k = 1; k <= 6; ++ k) {
for (int i = 1; i <= 6; ++ i)
if ( i <= n - k ) {
add_mod(ans, dp[n-k][i][0]);
}
for(int i = 1; k + i <= 6; ++ i)
if ( i <= n - k ) {
add_mod(ans, dp[n-k][i][1]);
}
}
add_mod(ans, ans);
}
printf("Case #%d: %d\n", ++ cas, ans);
}
return 0;
}
POJ 2763 Housewife Wind
题意:
给一棵树,边上有权值。两种操作,1)询问两点间路径权值和 2)修改一条边的值
思路:
从《挑战程序设计竞赛》上学到 LCA + 树状数组的解法。
利用 RMQ LCA 中求出的欧拉序列,加上树状数组,可以方便的维护路径和。
void dfs(int fa, int u, int d) { // father, now, depth
vs[timer] = u; // 记录欧拉序列
id[u] = timer; // 每个顶点在欧拉序列中的最小下标
dep[timer ++] = d; // 欧拉序列中每个下标对应深度
// 每条边在欧拉序列中对应一个区间, in[], out[] 分别是左右端点
// 当沿着这条边进入一颗子树的时候,需要加上该边权值
// 当沿着这条边离开子树的时候,要减去该边权值
for(int i = head[u]; i != -1; i = E[i].nxt) {
const Edge& e = E[i];
if ( e.v == fa ) continue;
BIT::upd(timer, w[e.id], n << 1);
in[e.id] = timer;
dfs(u, e.v, d+1);
vs[timer] = u;
BIT::upd(timer, -w[e.id], n << 1);
out[e.id] = timer;
dep[timer ++] = d;
}
}
容斥练习
hdu 2204 Eddy’s爱好
题意:求 1 - n 中可以表示成
xy,y>1
的数的个数
f(k) : 可以表示成
xk<=n
的数
可以通过容斥求出答案
例如
26=82=43
,所以
x6
之前被算了两次,所以 f(6) 的系数是 -1
最后对每个数,都只计了
xk
:x 最小,k 最大的形式,所以不会重复。
关于 f(k) 的计算,可以用 f(k) = floor ( pow( n, 1.0 / k + eps ) )
而 acdreamer 给了一种更准确的方法
const int prime[]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59};
ll n, ans, tot;
ll get(int k) {
ll ret = pow(n, 1.0 / k) + 1e-8;
-- ret;
return ret;
}
void dfs(int cnt, int pos, int mul) {
if ( cnt ) {
ans += get(mul) * ( ( cnt & 1 ) ? 1 : -1 );
}
if ( cnt == 3 ) return;
for(int i = pos + 1; i < tot; ++ i) {
if ( mul * prime[i] >= 60 ) break;
dfs( cnt + 1, i, mul * prime[i] );
}
}
int main() {
tot = sizeof(prime) / sizeof(int);
while ( cin >> n ) {
ans = 0;
dfs(0, -1, 1);
cout << ans + 1 << endl;
}
return 0;
}
codeforces 588D - Duff in Beach
bi 可以表示成
{0…n-1}{0…n-1}{0…(l%n - 1 + n) % n} 括号中的数字是 ai 中的下标,每个括号中的元素为一段
(i, j) 表示从第0段开始长度为i,以ai中下标为j的元素结束
然后对每个(i, j)算出起始段的可行个数 x,将 x * dp(i, j) 增加到答案。
PS:有爆ll的点。。