此处有目录↑
http://acm.hdu.edu.cn/showproblem.php?pid=5640
2 2 3 2 5
3 4 hint: For the first testcase you can divide the into one cake of 2*2 , 2 cakes of 1*1
题目大意:有一块n*m的蛋糕,每次只切1刀,切下一个正方形的蛋糕,问最多有多少块正方形的蛋糕?
大致思路:由于只能切一刀,所以必定是以较短边为变长的正方形,剩下的矩形蛋糕继续重复切蛋糕的过程
只想到裸的模拟,没有想到优化后的方法,幸亏没有卡
#include <cstdio>
using namespace std;
int main() {
int T,a,b,ans;
scanf("%d",&T);
while(T--) {
scanf("%d%d",&a,&b);
ans=0;
while(a>0&&b>0) {
++ans;
if(a>b) {
a-=b;
}
else if(a<b) {
b-=a;
}
else
break;
}
printf("%d\n",ans);
}
return 0;
}
#include <cstdio>
using namespace std;
int main() {
int T,a,b,ans;
scanf("%d",&T);
while(T--) {
scanf("%d%d",&a,&b);
ans=0;
while(a>0&&b>0) {
if(a>b) {//用除法和取模快速求得在某一方向连续切割的次数,防止 1 1000 这样的极端数据
ans+=a/b;
a%=b;
}
else {
ans+=b/a;
b%=a;
}
}
printf("%d\n",ans);
}
return 0;
}
3 4 1 3 6 2 4 6 2 1 3 4 8 1 6 7
invalid valid valid hint: For test case #1:The path 1→3 skipped the middle point $2$, so it's invalid. For test case #2:The path 1 →3 doesn't skipped the middle point $2$, because the point 2 has been through, so it's valid. For test case #2:The path 8 →1 →6 →7 doesn't have any the middle point 2, so it's valid.
题目大意:判断一个安卓图形解锁是否合法。合法的密码满足一下规则:
1. 密码至少经过四个点。
2. 不能重复经过同一个点。
3. 路径上的中间点不能跳过,除非已经被经过(34273427 是合法的,但 37243724 不合法)
大致思路:规则1,2很好判断,规则3只需要开一个数据cross[i][j],表示i,j之间必须经过的点是cross[i][j],为了方便,用0表示不用经过点,并且vis[0]=true
坑点:点是从0开始一直可以取到int最大值
写的时候偷懒,至判断了当前的点是否在1~9之间,忘了判断上一个点,导致终测时越界
#include <cstdio>
#include <cstring>
using namespace std;
const int mod=1000000007;
int T,k,num[11],cross[11][11];
bool vis[11],valid;
int main() {
memset(cross,0,sizeof(cross));
cross[1][3]=cross[3][1]=2;
cross[1][7]=cross[7][1]=4;
cross[1][9]=cross[9][1]=5;
cross[2][8]=cross[8][2]=5;
cross[3][7]=cross[7][3]=5;
cross[3][9]=cross[9][3]=6;
cross[4][6]=cross[6][4]=5;
cross[7][9]=cross[9][7]=8;
scanf("%d",&T);
while(T--) {
valid=true;
memset(vis,false,sizeof(vis));
scanf("%d",&k);
if(k<4)
valid=false;
for(int i=0;i<k;++i)
scanf("%d",num+i);
if(1<=num[0]&&num[0]<=9) {
vis[0]=vis[num[0]]=true;
for(int i=1;i<k;++i) {
if(1<=num[i]&&num[i]<=9) {
if(vis[num[i]])
valid=false;
else
vis[num[i]]=true;
if(!vis[cross[num[i-1]][num[i]]])
valid=false;
}
else {
valid=false;
break;
}
}
}
else
valid=false;
printf("%s\n",valid?"valid":"invalid");
}
return 0;
}
2 2 4
676 456950 hint: All the order that has length 2 are legal. So the answer is 26*26. For the order that has length 4. The illegal order are : "aaaa" , "bbbb"…….."zzzz" 26 orders in total. So the answer for n == 4 is 26^4-26 = 456950
题目大意:长度为n的字符指令(只有小写字母),若任意字符连续出现次数小于等于3次,则该指令合法,求所有长度为n且合法的指令数目,答案模1000000007
大致思路:看到这种求方案数的就应该想到DP,然后想如何转移即可
刚开始没有思路,感觉这次只能A 2题了(没想到最后真的只有2题...),便弃疗去玩游戏,不一会儿便想到一定是DP,最终找到了转移方程,然而WA了,又开始怀疑,想到容斥,数学方法也WA(其实开始就想到不可能,重复的情况太多),然后继续回来想转移方程,发现有一部分算重了,导致后面算的结果出现问题,改了后终于AC
看到题解说这题一眼就能看出来是数位DP,然而没学过(貌似只学过最简单的几种DP,其他都不知道...)
题解的复杂度是:O(26*26*n*T),我的这个复杂度是:O(26*n*T),看到有人用O(n)的方法直接预处理出所有答案(目瞪口呆)(貌似都可以预处理的...
O(n)的算法思路大致是:去掉26字母这一维,将所有的合并,dp[i][j]表示前i个字符,末尾有j个字符相同的方案数
状态转移方程:dp[i][1]=25*(dp[i-1][1]+dp[i-1][2]+dp[i-1][3]);//所有i-1字符指令的末尾加上不同于前一个字符的字符,共25个
dp[i][2]=dp[i-1][1];
dp[3]=dp[i-1][2];
#include <cstdio>
#include <cstring>
using namespace std;
const int mod=1000000007;
int T,n;
int dp[2005][27];
int main() {
for(int j=0;j<26;++j)
dp[1][j]=1;
dp[0][26]=0;
dp[1][26]=26;
scanf("%d",&T);
while(T--) {
scanf("%d",&n);
for(int i=2;i<=n;++i) {
dp[i][26]=0;//为了减少计算,dp[i][26]表示前i个字符形成的所有合法方案数
for(int j=0;j<26;++j) {
dp[i][j]=dp[i-1][26];//dp[i][j]的方案数就是 n-1个字符形成的所有合法方案数 - 这些方案数中后面3个字符均为j的方案数
if(i>3) {
dp[i][j]=(dp[i][j]-dp[i-3][j]+mod)%mod;//减去不合法的方案数(后面三个字符均为j时,其方案数就等于dp[i-3][j])
dp[i-2][j]=(dp[i-2][j]-dp[i-3][j]+mod)%mod;//由于从i-3开始均为j的方案数已经被排除,所以dp[i-2][j]也应该减去dp[i-3][j],即保证dp[i-2][j]的方案数均为末尾是j的方案数,第i-3个字符不是j,防止重复计算
dp[i-1][j]=(dp[i-1][j]-dp[i-3][j]+mod)%mod;
}
dp[i][26]=(dp[i][26]+dp[i][j])%mod;
}
}
printf("%d\n",dp[n][26]);
}
return 0;
}
————————————————————3题的旅游分割线————————————————————
2 2 3
2 2 Hint: For test case #1:the man who report number 1 is the man with label 1, so the man with label 2 is survivor. For test case #2:the man who report number 1 is the man with label 1, so the man with label 1 is out. Again the the man with label 2 counts 1, the man with label 3 counts 2, so the man who report number 2 is the man with label 3. At last the man with label 2 is survivor.
题目大意:n个人(编号为1,2,3……n),从第一个人开始数1,第一次数到1的人出去,下一个人从1开始重新数,第二次数到2的人出去……第n-1次,数到n-1的人出去,问最后留下的人的编号是多少。
知道如何用O(n)的方法求约瑟夫环,但是变形后就不知所措了,学得太死板了
发现好多人都是打表过的,感觉自己太年轻了,完全没想过打表
官方题解:
约瑟夫问题的一个变种,然而题目全部是在唬人,就是一个简单的递推。虽然我知道有人会打表。。。
我们看看裸的约瑟夫是怎么玩的:n 个人,每隔 k 个删除。
由于我们只关心最后一个被删除的人,并不关心最后的过程,所以,我们没有必要也不能够模拟整个过程。我们用递推解决。假设有n个人围成环,标号为[0,n−1]从0开始的好处是取模方便),每数k个人杀一个的情况下,最后一个存活的人的编号是f[n]。
我们有f[1]=0,这不需要解释。
接着考虑一般情况f[n],第一个杀死的人的编号是k−1,杀死后只剩下n−1个人了,那么我们重新编号!
原来编号为k的现在是0号,也就是编号之间相差3我们只要知道现在n−1个人的情况最后是谁幸存也就知道n个人的情况是谁幸存。幸运的是f[n−1]已经算出来了那f[n]就是在f[n−1]的基础上加上一个k即可不要忘记总是要取模。
所以递推式子是: f[i]={ 0 i=1 (f[i - 1] + k) mod i other
此题只用在原版约瑟夫问题上加一维,由于依次隔 1,2,3...n−1 个人删除,所以用 f[i][j] 表示 i 个人,依次隔 j,j+1...j+i−1 个人的幸存者标号。
根据刚才的重标号法,第一次 j−1 号出局,从 j 开始新的一轮,从 j+1 开始清除,剩余 i−1 个人,也有递推式子:
f[i][j]={ 0 i=1 (f[i - 1][j+1] + j) mod i other
答案就是 f[n][1]+1(将标号转移到 [1,n]),问题轻松解决。
复杂度:预处理 O(n2),查询 O(1),总复杂度 O(n2)。由于可以滚动数组以及常数太小,所以 n 给了 5000
#include <cstdio>
#include <cstring>
using namespace std;
const int mod=1000000007;
int T,n;
int dp[2][5005],ans[5005];
int main() {
for(int j=0;j<=5000;++j)
dp[1][j]=0;
ans[1]=1;
for(int i=2;i<=5000;++i) {
for(int j=0;j<=5000;++j)
dp[i&1][j]=(dp[(i+1)&1][j+1]+j)%i;
ans[i]=dp[i&1][1]+1;
}
scanf("%d",&T);
while(T--) {
scanf("%d",&n);
printf("%d\n",ans[n]);
}
return 0;
}