2019-08-22
21:59:38
小A买彩票
题目链接:https://ac.nowcoder.com/acm/contest/549/C
今天无聊逛牛客,找到了这道简单dp题
dp[i][j]代表第i张彩票价格为j的可能购票个数,那么dp[i][j] = dp[i-1][j-1]+dp[i-1][j-2]+dp[i-1][j-3]+dp[i-1][j-4]
(这么简单的式子居然推了我15分钟T^T,果然还是太菜了)
然后用sum记录 j >= 3*n 的个数,最后用gcd求sum和4^n(总数)的公约数——就完事了
1 #include2 using namespace std; 3 #define ll long long 4 #define inf 0x3f3f3f 5 const int N = 1e3+10; 6 ll dp[N][N]; 7 ll gcd(ll a,ll b) 8 { 9 if(a<b) 10 swap(a,b); 11 if(a%b) 12 { 13 ll c = a%b; 14 a = b; 15 b = c; 16 return gcd(a,b); 17 } 18 return b; 19 20 } 21 int main() 22 { 23 int n; 24 while(cin>>n) 25 { 26 if(!n) 27 { 28 cout<<1<<"/"<<1<<endl; 29 continue; 30 } 31 memset(dp,0,sizeof(dp)); 32 for(int i=1;i<=4;i++) 33 dp[1][i] = 1; 34 for(int i=2;i<=n;i++) 35 { 36 for(int j=i;j<=4*i;j++) 37 dp[i][j] = dp[i-1][j-1]+dp[i-1][j-2]+dp[i-1][j-3]+dp[i-1][j-4]; 38 } 39 ll sum = 0; 40 //dp[i][3*n] 41 for(int j=3*n;j<=4*n;j++) 42 sum+=dp[n][j]; 43 44 ll maxn = pow(4,n); 45 ll gcdd = gcd(maxn,sum); 46 cout< "/"< endl; 47 } 48 return 0; 49 }
2019-08-22
01:53:55
被3整除的子序列
题目链接:https://ac.nowcoder.com/acm/problem/21302
又是无聊逛牛客看到一道特别眼熟的题,打开一看是曾经AC过了的题
但是点开解题的代码才发现以前是暴力过的...于是就用dp重写了三份
一份是用子序列dp解决,另外两份用的是区间dp
dp[i][j]前i个数中合成j的方法数(子区间个数),用这方法要注意0%3也等于0,所以0的作用相当于3(被这个小细节坑了好多时间T^T)
1 #include2 using namespace std; 3 #define ll long long 4 #define inf 0x3f3f3f 5 const ll mod = 1e9+7; 6 const int N = 1e3+10; 7 ll dp[N][N]; 8 char s[N]; 9 ll f(char a) 10 { 11 return a-'0'; 12 } 13 int main() 14 { 15 ios::sync_with_stdio(false); 16 while(cin>>(s+1)) 17 { 18 19 int ans = 0; 20 int len = strlen(s+1); 21 for(int i=1; i<=len; i++) 22 { 23 24 for(int j=0; j<1000; j++) 25 { 26 dp[i][j] = dp[i-1][j]; 27 //如果 s[i] == j 那么前i个数 组成的j的个数+1 28 if(j == f(s[i]) ) dp[i][j]++; 29 // s[i]不能单独作为组成j的个数+1,所以寻找 j-s[i]的个数 ,判断能和 s[i]组成j的个数有多少 30 // 这里还要判断一次等于是因为序列中可能存在0 比如03 和3 为两个不同的序列 ,所以要多个等于的判断(真tm坑!) 31 if(j >= f(s[i]) ) dp[i][j] = dp[i-1][j - f(s[i]) ] + dp[i][j]; 32 //寻找能和s[i]组成j的个数 , 因为s[i]>j ,所以能和s[i]组成j(s[i]+x=j)的x必为负数,跳过 33 34 //if(j 35 dp[i][j] =dp[i][j] % mod; 36 } 37 } 38 for(int j=0; j<=1000; j++) 39 if(j%3==0) ans = (ans+dp[len][j])%mod; 40 cout< 41 42 } 43 return 0; 44 }endl;
dp[i][j][k]表示从区间 i 到区间 j %3等于k的序列的个数
1 #include2 using namespace std; 3 #define ll long long 4 #define inf 0x3f3f3f 5 const ll mod = 1e9+7; 6 const int N = 1e3+10; 7 ll dp[N][N][5]; 8 char s[N]; 9 ll f(char a) 10 { 11 return a-'0'; 12 } 13 int main() 14 { 15 char s[1000]; 16 while(cin>>(s+1)) 17 { 18 19 int len = strlen(s+1); 20 for(int i = 1; i <= len; i++) 21 { 22 dp[i][i][(f(s[i]))%3]=1; 23 } 24 for(int i = 1; i <= len; i++) 25 { 26 for(int j = i+1; j <= len; j++) 27 { 28 if(f(s[j])% 3 == 0) 29 { 30 dp[i][j][0] = 2*dp[i][j-1][0] + 1; 31 dp[i][j][1] = 2*dp[i][j-1][1]; 32 dp[i][j][2] = 2*dp[i][j-1][2]; 33 } 34 else if(f(s[j])% 3 == 1) 35 { 36 dp[i][j][0] = dp[i][j-1][0] + dp[i][j-1][2]; 37 dp[i][j][1] = dp[i][j-1][1] + dp[i][j-1][0] + 1; 38 dp[i][j][2] = dp[i][j-1][1] + dp[i][j-1][2]; 39 } 40 else if(f(s[j])%3 == 2) 41 { 42 dp[i][j][0] = dp[i][j-1][0] + dp[i][j-1][1]; 43 dp[i][j][1] = dp[i][j-1][2] + dp[i][j-1][1]; 44 dp[i][j][2] = dp[i][j-1][0] + dp[i][j-1][2] + 1; 45 } 46 for(int k = 0; k < 3; k++) 47 dp[i][j][k] = dp[i][j][k] % mod; 48 49 } 50 } 51 cout< 1][len][0]<<endl; 52 } 53 return 0; 54 }
化三维为二维的话,dp[i][j]表示前i个数组成的区间中%3等于j的子序列的个数
1 #include2 using namespace std; 3 #define inf 0x3f3f3f 4 typedef long long ll; 5 const ll mod = 1e9+7; 6 const int N=1e3+10; 7 typedef long long ll; 8 char vis[N]; 9 ll dp[N][5];//保存从0到i上,余数为0,1,2的子序列数目 10 ll f(char a) 11 { 12 return a-'0'; 13 } 14 int main() 15 { 16 ios::sync_with_stdio(false); 17 string num; 18 while(cin>>num) 19 { 20 21 int n=num.length(); 22 for(int i=0; i ) 23 { 24 dp[i][f(num[i])%3]=1; 25 } 26 for(int i=0; i ) 27 { 28 if(num[i]%3==0) 29 { 30 dp[i][0]=2*dp[i-1][0]+1;//自己本身为子序列所以加一 31 dp[i][1]=2*dp[i-1][1]; 32 dp[i][2]=2*dp[i-1][2]; 33 } 34 else if(num[i]%3==1) 35 { 36 dp[i][0]=dp[i-1][0]+dp[i-1][2]; 37 dp[i][1]=dp[i-1][1]+dp[i-1][0]+1; 38 dp[i][2]=dp[i-1][2]+dp[i-1][1]; 39 } 40 else if(num[i]%3==2) 41 { 42 dp[i][0]=dp[i-1][0]+dp[i-1][1]; 43 dp[i][1]=dp[i-1][1]+dp[i-1][2]; 44 dp[i][2]=dp[i-1][2]+dp[i-1][0]+1; 45 } 46 for(int j=0; j<3; j++) dp[i][j]%=mod; 47 48 } 49 cout< 1][0]<<endl; 50 } 51 return 0; 52 }
2019-08-22
15:05:21
删括号
题目链接:https://ac.nowcoder.com/acm/problem/21303?&headNav=acm
这道题很明显是道判定性dp题
本来还在琢磨着怎么建立状态方程,突然发现题目给的字符串长度小于等于100,于是果断放弃dp写起了dfs
(然而写条件和调bug一共用了快一个小时的时间,这么想想还不如用dp来得快)
先贴上dfs
1 #include2 using namespace std; 3 #define ll long long 4 #define inf 0x3f3f3f 5 const int N = 1e2 + 10; 6 string s, t; 7 int a[N], b[N]; 8 bool dfs(int l1, int r1, int l2, int r2) 9 { 10 if(l2 >= r2) 11 { 12 return true; 13 } 14 if(l1 >= r1) 15 { 16 return false; 17 } 18 int cnt = 0; 19 int index_a_l = l2; 20 int index_a_r = l2; 21 int index_b_l = l1; 22 int index_b_r = l1; 23 while(index_a_r < r2 && index_b_r < r1) 24 { 25 if(t[index_a_r++] == '(') 26 { 27 cnt++; 28 } 29 else 30 { 31 cnt--; 32 } 33 if(cnt == 0) 34 { 35 int acnt = 0; 36 while(index_b_r < r1) 37 { 38 if(s[index_b_r++] == '(') 39 { 40 acnt++; 41 } 42 else 43 { 44 acnt--; 45 } 46 if(acnt == 0) 47 { 48 if(!dfs(index_b_l + 1,index_b_r - 1,index_a_l + 1, index_a_r - 1)) 49 { 50 index_b_l = index_b_r; 51 if(index_b_r == r1) 52 { 53 return false; 54 } 55 } 56 else 57 { 58 index_b_l = index_b_r; 59 index_a_l = index_a_r; 60 break; 61 } 62 } 63 } 64 } 65 } 66 return index_a_r == r2; 67 } 68 int main() 69 { 70 ios::sync_with_stdio(false); 71 while(cin>>s>>t) 72 { 73 if(dfs(0,s.length(),0,t.length())) 74 { 75 cout<<"Possible"<<endl; 76 } 77 else 78 { 79 cout<<"Impossible"<<endl; 80 } 81 } 82 }
下面在贴上dp
dp[i][j][k] 表示 a1...ai和b1...bj在额外多删除k个 ' ( ' 时能否匹配
(代码里写有详细的注释)
1 #include2 using namespace std; 3 #define ll long long 4 #define inf 0x3f3f3f 5 const int N = 1e2+10; 6 char a[N],b[N]; 7 bool dp[N][N][N]; 8 int main() 9 { 10 while(cin>>(a+1)) 11 { 12 memset(dp,false,sizeof(dp)); 13 cin>>(b+1); 14 dp[0][0][0] = 1; 15 int n = strlen(a+1); 16 int m = strlen(b+1); 17 for(int i=0 ; i ) 18 { 19 for(int j = 0; j ) 20 { 21 for(int k = 0; k<=n; k++) 22 { 23 if(dp[i][j][k]) 24 { 25 if(!k && a[i+1] == b[j+1]) dp[i+1][j+1][k] = true; 26 if(a[i+1]=='(') dp[i+1][j][k+1] = true; 27 else if( k == 0 && a[i+1] == ')') dp[i+1][j][k] = false; 28 //a1...ai 和b1...bj 在额外多删除k个'('时已经匹配 29 //如果k等于0说明此时a1...ai和b1...bj是额外多删除0个'('才匹配,即两者已经完全匹配 30 //那么 此时 dp[i+1][j][k]代表 a1...ai+1和b1...bj还需要额外多删除 0-1 = -1个'('即需要额外添加一个'('才匹配,显然这是不合法的 31 //如果k大于0说明此时a1...ai和b1...bj是额外多删除k个'('才匹配了 32 //那么由于ai+1为')',所以前面可以额外少删除一个'('使得a1...ai+1和b1...bj匹配 33 else if( k > 0 && a[i+1] == ')') dp[i+1][j][k-1] = true; 34 35 36 } 37 } 38 } 39 } 40 if(dp[n][m][0]) //a1...an和b1...bm能否额外多删除0个'('使他们匹配 41 cout<<"Possible"<<endl; 42 else cout<<"Impossible"<<endl; 43 } 44 return 0; 45 }
2019-08-28
02:52:30
Labyrinth
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4826
今天模拟赛的一道(假)水题
刚开始做的时候看到题目数据范围并不是很大,于是很快就写起dfs,但是提交的时候很遗憾的TLE了,本来还以为又是哪里出bug了,就反复改反复提交(好傻呀...)... 于是就一直TLE,直到看了眼别人的提交,大家的运行结果都是TLE,这才发现不太对劲...本来这时候我应该很快反应过来要用dp的,但我却跑去做别的题去了...最后只留了一点时间来推dp的状态方程,并且发现它要考虑多个方向的状态,好像需要点时间处理,于是就很尴尬的错过了Accept的机会...
赛后看了一下别人的题解,顿时恍然大悟,然后就跟着状态方程摸着摸着把题Acccept了。。。
dp[ i ][ j ][ 0 ]表示从前一个状态往下走到 第 i 行 第 j 列时的最大金币数
dp[ i ][ j ][ 1 ]表示从前一个状态往上走到 第 i 行 第 j 列时的最大金币数
dp[ i ][ j ][ 2 ]表示从前一个状态往右走到 第 i 行 第 j 列时的最大金币数
下面贴代码
1 #include2 #define inf 0x3f3f3f3f 3 using namespace std; 4 const int N = 1e2 + 10; 5 int mp[N][N]; 6 int dp[N][N][3]; 7 int main() 8 { 9 int T; 10 scanf("%d", &T); 11 for (int t = 1; t <= T; ++t) 12 { 13 memset(dp, -inf, sizeof(dp)); 14 int n, m; 15 scanf("%d%d", &n, &m); 16 for (int i = 1; i <= n; ++i) 17 for (int j = 1; j <= m; ++j) 18 scanf("%d", &mp[i][j]); 19 dp[1][1][0] = mp[1][1]; 20 dp[1][1][1] = mp[1][1]; 21 dp[1][1][2] = mp[1][1]; 22 23 for (int i = 2; i <= n; ++i) 24 dp[i][1][0] = dp[i - 1][1][0] + mp[i][1]; 25 26 for (int j = 2; j <= m; ++j) 27 { 28 //往右走,可以从第一行开始,因为状态的累加是从左边一列累加的 29 //并且三种方向都可以 30 31 for (int i = 1; i <= n; ++i) 32 dp[i][j][2] = max(dp[i][j - 1][0], max(dp[i][j - 1][1], dp[i][j - 1][2])) + mp[i][j]; 33 34 //往下走。需要从第二行开始,因为状态的累加是从上一行开始的,第 1 行不可能是由第 0 行向下转移来的 35 //并且只能够从右边和上面得到 36 37 for (int i = 2; i <= n; ++i) 38 dp[i][j][0] = max(dp[i - 1][j][0], dp[i - 1][j][2]) + mp[i][j]; 39 //往上走,需要从倒数第二行开始,因为状态的累加是从下一行开始的, 第 n 行 不可能是由 第 n+1 行向上转移来的 40 //并且只能够从右边和下面得到 41 42 for (int i = n - 1; i >= 1; --i) 43 dp[i][j][1] = max(dp[i + 1][j][1], dp[i + 1][j][2]) + mp[i][j]; 44 } 45 int ans = max(dp[1][m][0], max(dp[1][m][1], dp[1][m][2])); 46 printf("Case #%d:\n", t); 47 printf("%d\n", ans); 48 } 49 return 0; 50 }
看别人的博客有只用两种状态进行转移的dp,这是因为除了第一列之外的每一行每一列的最大金币数都可
以由前一列向右转移得来,所以我们当 j >= 2 时我们肯定是要考虑到dp[ i ][ j - 1]的状态,而这个状态的表
达方式为 dp[ i ][ j - 1 ][ 1 ]或者dp[ i ][ j - 1 ][ 0 ] (两者的值是一样的),
1 #include2 using namespace std; 3 const int N = 105; 4 const int INF = 0x3f3f3f3f; 5 int n, m, g[N][N], dp[N][N][2]; 6 7 void init() 8 { 9 scanf("%d%d", &n, &m); 10 for (int i = 1; i <= n; i++) 11 for (int j = 1; j <= m; j++) 12 scanf("%d", &g[i][j]); 13 } 14 15 int solve() 16 { 17 18 for (int i = 1; i <= m; i++) 19 dp[0][i][0] = dp[n + 1][i][1] = -INF; 20 21 dp[0][1][0] = 0; 22 for (int i = 1; i <= n; i++) 23 { 24 dp[i][1][0] = dp[i - 1][1][0] + g[i][1]; 25 dp[i][1][1] = -INF; 26 } 27 28 for (int j = 2; j <= m; j++) 29 { 30 31 for (int i = 1; i <= n; i++) 32 dp[i][j][0] = max(dp[i - 1][j][0], max(dp[i][j - 1][0], dp[i][j - 1][1])) + g[i][j]; 33 34 for (int i = n; i >= 1; i--) 35 dp[i][j][1] = max(dp[i + 1][j][1], max(dp[i][j - 1][0], dp[i][j - 1][1])) + g[i][j]; 36 } 37 return max(dp[1][m][0], dp[1][m][1]); 38 } 39 40 int main() 41 { 42 int cas; 43 scanf("%d", &cas); 44 for (int i = 1; i <= cas; i++) 45 { 46 init(); 47 printf("Case #%d:\n%d\n", i, solve()); 48 } 49 return 0; 50 }
所以代码我们可以简化,如下