BestCoder Round #75
2016-03-30 23:45
260 查看
A题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5640
官方题解:
显然这很像求最大公约数的过程嘛,看这张神图:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202012/05/e6a44bbd615d12ef2e54dc0cb8f8a2d4.gif)
所以每次 \gcdgcd 的时候累加答案即可,复杂度 O(\log\max(n,
m)T)O(logmax(n,m)T)。
当然你要是循环减应该也放过了。
我的思考:就是一直长的减短的,减到不能再减为止
B题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5641
官方链接:
一个简单的模拟题,首先判断序列长度是否合法,接着判断 s_isi 是否在 [1,
9][1,9],最后扫一遍看看有没有重复以及跨过中间点的情况即可。
复杂度:O(nT)O(nT)。
我的思考:简单模拟,但我老是会出一些错误,导致WA,真是难受
C题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5642
官方题解:
数一个长度为 nn 的序列
, 并且序列中不能出现长度大于 33 的连续的相同的字符
, 这玩意就是一个数位DP嘛。 定义 d[i][j][k]d[i][j][k] 为处理完 ii 个字符
, 结尾字符为 'a'+j′a′+j ,
结尾部分已重复出现了 kk 次的方案数。
刷表转移一下就好啦。
复杂度:O(26 * 26 * nT)O(26∗26∗nT)
我的理解:递推f
[i]代表长度为n末尾重复个数为i的数的个数,那么当n>=3时
f
[1]=sum(f[n-1][1]+f[n-1][2]+f[n-1][3])*25 f
[2]=f[n-1][2] f
[3]=f[n-1][3]
D题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5643
官方题解:
约瑟夫问题的一个变种,然而题目全部是在唬人,就是一个简单的递推。虽然我知道有人会打表。。。
我们看看裸的约瑟夫是怎么玩的:nn 个人,每隔 kk 个删除。
由于我们只关心最后一个被删除的人,并不关心最后的过程,所以,我们没有必要也不能够模拟整个过程。我们用递推解决。假设有nn个人围成环,标号为[0,n-1][0,n−1]从00开始的好处是取模方便),每数kk个人杀一个的情况下,最后一个存活的人的编号是f
f[n]。
我们有f[1]=0f[1]=0,这不需要解释。
接着考虑一般情况f
f[n],第一个杀死的人的编号是k-1k−1,杀死后只剩下n-1n−1个人了,那么我们重新编号!
原来编号为k的现在是00号,也就是编号之间相差33我们只要知道现在n-1n−1个人的情况最后是谁幸存也就知道nn个人的情况是谁幸存。幸运的是f[n-1]f[n−1]已经算出来了那f
f[n]就是在f[n-1]f[n−1]的基础上加上一个kk即可不要忘记总是要取模。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202012/05/dd08820945ffd7b3ef2924268079faad.png)
所以递推式子是: f[i] = \begin{cases} & \text{ 0 \ \ \ \
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ } i=1 \ & \text{ (f[i - 1] + k) mod i \ \ \ \ \ \ } other \end{cases}f[i]={ 0 i=1 (f[i - 1] + k) mod i other
此题只用在原版约瑟夫问题上加一维,由于依次隔 1, 2, 3...n - 11,2,3...n−1 个人删除,所以用 f[i][j]f[i][j] 表示 ii 个人,依次隔 j,
j + 1... j + i - 1j,j+1...j+i−1 个人的幸存者标号。
根据刚才的重标号法,第一次 j - 1j−1 号出局,从 jj 开始新的一轮,从 j
+ 1j+1 开始清除,剩余 i
- 1i−1 个人,也有递推式子:
f[i][j] = \begin{cases} & \text{ 0 \ \ \ \ \ \ \
\ \ \ \ \ \ \ \ \ \ \ \ \ \ } i=1 \ & \text{ (f[i - 1][j+1] + j) mod i \ \ \ \ \ \ } other \end{cases}f[i][j]={ 0 i=1 (f[i - 1][j+1] + j) mod i other
答案就是 f
[1] + 1f[n][1]+1(将标号转移到 [1,
n][1,n]),问题轻松解决。
复杂度:预处理 O(n^2)O(n2),查询 O(1)O(1),总复杂度 O(n^2)O(n2)。由于可以滚动数组以及常数太小,所以 nn 给了 50005000(其实是出题人不会别的算法嘿嘿嘿)。
我的思考:约瑟夫问题主要是可以多往重新编号方面想,这样比较容易有思路
E题 链接http://acm.hdu.edu.cn/showproblem.php?pid=5644
官方题解:
这是一个费用流建模题 , BC好久都没有费用流题了……
这个题关键是要满足每天的飞行员数量充足 , 那么可能是一开始就有的飞行员 , 或者某飞行员休假归来 , 或者新招募的飞行员。
将每一天拆成两个点 ,分别表示这一天开始时候飞行员的状态和这一天工作完成后飞行员的状态 , 分别为 X_iXi , Y_iYi
先看看建图:
从 S 向每个 X_iXi 连一条容量为 P_iPi,费用为 00 的有向边。
从每个 Y_iYi向
T 连一条容量为 P_iPi,费用为 00 的有向边。
从 S 向 Y_1Y1 连一条容量为 kk ,
费用为 00 的有向边。
从 S 向每个 Y_i(i\ge P)Yi(i≥P) 连一条容量为无穷大,费用为 QQ 的有向边。(新招募的)
从每个 XiXi 向 Xi+1Xi+1 连一条容量为无穷大,费用为 00 的有向边。
从每个YiYi向Yi+1Yi+1连一条容量为无穷大,费用为 00 的有向边。
对于每一个休假计划 , 从每一个X_iXi 向 Y_{i+T_i}Yi+Ti 连一条容量为无穷大费用为 S_iSi 的有向边
然后开心的跑一跑最小费用最大流看一看是不是满流就可以判断是否可行啦……
我的思考:想是想不到,但是看到题解能理解,应该是网络流题目做太少了。最小费用最大流
官方题解:
显然这很像求最大公约数的过程嘛,看这张神图:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202012/05/e6a44bbd615d12ef2e54dc0cb8f8a2d4.gif)
所以每次 \gcdgcd 的时候累加答案即可,复杂度 O(\log\max(n,
m)T)O(logmax(n,m)T)。
当然你要是循环减应该也放过了。
我的思考:就是一直长的减短的,减到不能再减为止
#include<cstdio> #include<cstring> #include<algorithm> const int N=1010; int main() { int T,n,cnt,m,t; scanf("%d",&T); while(T--) { cnt=0; scanf("%d%d",&n,&m); if(n==m) { printf("1\n"); continue; } while(n!=m) { if(n<m) { t=n; n=m; m=t; } if(m==0) break; cnt+=n/m; n%=m; } printf("%d\n",cnt); } return 0; }
B题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5641
官方链接:
一个简单的模拟题,首先判断序列长度是否合法,接着判断 s_isi 是否在 [1,
9][1,9],最后扫一遍看看有没有重复以及跨过中间点的情况即可。
复杂度:O(nT)O(nT)。
我的思考:简单模拟,但我老是会出一些错误,导致WA,真是难受
#include<cstdio> #include<cstring> using namespace std; int g[100][100]; void init() { memset(g,0,sizeof(g)); g[1][3]=g[3][1]=2; g[4][6]=g[6][4]=5; g[7][9]=g[9][7]=8; g[1][7]=g[7][1]=4; g[2][8]=g[8][2]=5; g[3][9]=g[9][3]=6; g[1][9]=g[9][1]=5; g[3][7]=g[7][3]=5; } int main() { init(); int T,i,flag,n,pre,vis[10],a[10]; scanf("%d",&T); while(T--) { memset(vis,0,sizeof(vis)); scanf("%d",&n); flag=1; for(i=1;i<=n;i++) { scanf("%d",&a[i]); if(a[i]<1||a[i]>9) flag=0; } if(!flag) {printf("invalid\n");continue;} if(n<4) {printf("invalid\n");continue;} pre=a[1]; vis[a[1]]=1; for(i=2;i<=n;i++) { if(g[pre][a[i]]&&!vis[g[pre][a[i]]]) {flag=0;break;} if(vis[a[i]]) {flag=0;break;} vis[a[i]]=1; pre=a[i]; } if(flag) printf("valid\n"); else printf("invalid\n"); } return 0; }
C题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5642
官方题解:
数一个长度为 nn 的序列
, 并且序列中不能出现长度大于 33 的连续的相同的字符
, 这玩意就是一个数位DP嘛。 定义 d[i][j][k]d[i][j][k] 为处理完 ii 个字符
, 结尾字符为 'a'+j′a′+j ,
结尾部分已重复出现了 kk 次的方案数。
刷表转移一下就好啦。
复杂度:O(26 * 26 * nT)O(26∗26∗nT)
我的理解:递推f
[i]代表长度为n末尾重复个数为i的数的个数,那么当n>=3时
f
[1]=sum(f[n-1][1]+f[n-1][2]+f[n-1][3])*25 f
[2]=f[n-1][2] f
[3]=f[n-1][3]
#include<cstdio> using namespace std; #define LL __int64 const LL mod=1000000007; LL f[2005][4]; void init() { int i,j; f[1][0]=f[1][1]=26; f[2][1]=26*25; f[2][2]=26; f[2][0]=f[2][1]+f[2][2]; f[3][1]=26*26*25; f[3][2]=26*25; f[3][3]=26; f[3][0]=f[3][1]+f[3][2]+f[3][3]; for(i=4;i<=2000;i++) { LL s=0; f[i][1]=f[i-1][0]*25%mod; for(j=2;j<=3;j++) f[i][j]=f[i-1][j-1]; for(j=1;j<=3;j++) s=(s+f[i][j])%mod; f[i][0]=s; } } int main() { init(); int T,n; scanf("%d",&T); while(T--) { scanf("%d",&n); printf("%I64d\n",f [0]); } return 0; }
D题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5643
官方题解:
约瑟夫问题的一个变种,然而题目全部是在唬人,就是一个简单的递推。虽然我知道有人会打表。。。
我们看看裸的约瑟夫是怎么玩的:nn 个人,每隔 kk 个删除。
由于我们只关心最后一个被删除的人,并不关心最后的过程,所以,我们没有必要也不能够模拟整个过程。我们用递推解决。假设有nn个人围成环,标号为[0,n-1][0,n−1]从00开始的好处是取模方便),每数kk个人杀一个的情况下,最后一个存活的人的编号是f
f[n]。
我们有f[1]=0f[1]=0,这不需要解释。
接着考虑一般情况f
f[n],第一个杀死的人的编号是k-1k−1,杀死后只剩下n-1n−1个人了,那么我们重新编号!
原来编号为k的现在是00号,也就是编号之间相差33我们只要知道现在n-1n−1个人的情况最后是谁幸存也就知道nn个人的情况是谁幸存。幸运的是f[n-1]f[n−1]已经算出来了那f
f[n]就是在f[n-1]f[n−1]的基础上加上一个kk即可不要忘记总是要取模。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202012/05/dd08820945ffd7b3ef2924268079faad.png)
所以递推式子是: f[i] = \begin{cases} & \text{ 0 \ \ \ \
\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ } i=1 \ & \text{ (f[i - 1] + k) mod i \ \ \ \ \ \ } other \end{cases}f[i]={ 0 i=1 (f[i - 1] + k) mod i other
此题只用在原版约瑟夫问题上加一维,由于依次隔 1, 2, 3...n - 11,2,3...n−1 个人删除,所以用 f[i][j]f[i][j] 表示 ii 个人,依次隔 j,
j + 1... j + i - 1j,j+1...j+i−1 个人的幸存者标号。
根据刚才的重标号法,第一次 j - 1j−1 号出局,从 jj 开始新的一轮,从 j
+ 1j+1 开始清除,剩余 i
- 1i−1 个人,也有递推式子:
f[i][j] = \begin{cases} & \text{ 0 \ \ \ \ \ \ \
\ \ \ \ \ \ \ \ \ \ \ \ \ \ } i=1 \ & \text{ (f[i - 1][j+1] + j) mod i \ \ \ \ \ \ } other \end{cases}f[i][j]={ 0 i=1 (f[i - 1][j+1] + j) mod i other
答案就是 f
[1] + 1f[n][1]+1(将标号转移到 [1,
n][1,n]),问题轻松解决。
复杂度:预处理 O(n^2)O(n2),查询 O(1)O(1),总复杂度 O(n^2)O(n2)。由于可以滚动数组以及常数太小,所以 nn 给了 50005000(其实是出题人不会别的算法嘿嘿嘿)。
我的思考:约瑟夫问题主要是可以多往重新编号方面想,这样比较容易有思路
#include<cstdio> using namespace std; const int N=5005; int dp ; int main() { int T,n; scanf("%d",&T); while(T--) { dp[1]=0; scanf("%d",&n); for(int i=2;i<=n;i++) { dp[i]=(dp[i-1]+n-i+1)%i; } dp +=1; printf("%d\n",dp ); } return 0; }
E题 链接http://acm.hdu.edu.cn/showproblem.php?pid=5644
官方题解:
这是一个费用流建模题 , BC好久都没有费用流题了……
这个题关键是要满足每天的飞行员数量充足 , 那么可能是一开始就有的飞行员 , 或者某飞行员休假归来 , 或者新招募的飞行员。
将每一天拆成两个点 ,分别表示这一天开始时候飞行员的状态和这一天工作完成后飞行员的状态 , 分别为 X_iXi , Y_iYi
先看看建图:
从 S 向每个 X_iXi 连一条容量为 P_iPi,费用为 00 的有向边。
从每个 Y_iYi向
T 连一条容量为 P_iPi,费用为 00 的有向边。
从 S 向 Y_1Y1 连一条容量为 kk ,
费用为 00 的有向边。
从 S 向每个 Y_i(i\ge P)Yi(i≥P) 连一条容量为无穷大,费用为 QQ 的有向边。(新招募的)
从每个 XiXi 向 Xi+1Xi+1 连一条容量为无穷大,费用为 00 的有向边。
从每个YiYi向Yi+1Yi+1连一条容量为无穷大,费用为 00 的有向边。
对于每一个休假计划 , 从每一个X_iXi 向 Y_{i+T_i}Yi+Ti 连一条容量为无穷大费用为 S_iSi 的有向边
然后开心的跑一跑最小费用最大流看一看是不是满流就可以判断是否可行啦……
我的思考:想是想不到,但是看到题解能理解,应该是网络流题目做太少了。最小费用最大流
#include<cstdio> #include<cstring> #include<queue> #define inf 0x3f3f3f3f using namespace std; const int N=2005; const int M=200005; int dist ,pre ,head ; bool vis ; int tot,ans,cnt; struct node { int v,cap,next,cost; }edge[M]; void addedge(int u,int v,int w,int cost) { edge[tot].v=v; edge[tot].cap=w; edge[tot].cost=cost; edge[tot].next=head[u]; head[u]=tot++; edge[tot].v=u; edge[tot].cap=0; edge[tot].cost=-cost; edge[tot].next=head[v]; head[v]=tot++; } queue<int>q; bool spfa(int s,int t) { while(!q.empty()) q.pop(); for(int i=s;i<=t;i++) { dist[i]=inf; vis[i]=false; pre[i]=-1; } q.push(s); dist[s]=0; vis[s]=true; while(!q.empty()) { int u=q.front(); q.pop(); vis[u]=false; for(int i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].v; int cost=edge[i].cost; if(edge[i].cap&&dist[v]>dist[u]+cost) { dist[v]=dist[u]+cost; pre[v]=i; if(!vis[v]) { vis[v]=true; q.push(v); } } } } if(dist[t]==inf) return false; return true; } void addmaxflow(int t) { int i,maxn=inf; for(i=pre[t];i!=-1;i=pre[edge[i^1].v]) maxn=min(maxn,edge[i].cap); for(i=pre[t];i!=-1;i=pre[edge[i^1].v]) { edge[i].cap-=maxn; edge[i^1].cap+=maxn; } ans+=dist[t]*maxn; cnt+=maxn; } void maxflow(int s,int t) { while(spfa(s,t)) addmaxflow(t); } int p ; int a[6],b[5],T,n,k,i,m,P,Q; int main() { scanf("%d",&T); while(T--) { tot=ans=cnt=0; memset(head,-1,sizeof(head)); scanf("%d%d",&n,&k); int sum=0,s=0,t=2*n+1; for(i=1;i<=n;i++) { scanf("%d",&p[i]); sum+=p[i]; addedge(s,i,p[i],0); addedge(i+n,t,p[i],0); } scanf("%d%d%d",&m,&P,&Q); for(int i=1;i<=m;i++) scanf("%d%d",&a[i],&b[i]); addedge(s,1+n,k,0); for(int i=P;i<=n;i++) addedge(s,i+n,inf,Q); for(int i=n+1;i<2*n;i++) addedge(i,i+1,inf,0); for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) if(i+b[j]<=n) addedge(i,i+n+b[j],inf,a[j]); } maxflow(s,t); if(cnt==sum) printf("%d\n",ans); else printf("No solution\n"); } return 0; }
相关文章推荐
- 【1】JAVA---地址App小软件(AddressApp.class)(初步接触项目开发的分层思想)(表现层)
- Linux 上的常用文件传输方式介绍与比较
- 【1】JAVA---地址App小软件(AddressApp.class)(初步接触项目开发的分层思想)(表现层)
- 实用的 Python 之 feedparser
- 我的2016年决心书(老男孩教育在线课程班第一期)
- VBA基础
- 2016.3.30 OneZero站立会议
- c++判断一个字符串是否是数字
- Python itertools模块详解
- leetcode017 Letter Combinations of a Phone Number
- Azure上Linux虚拟机Mac地址的持久化
- 使用requestAnimationFrame和Canvas给按钮添加绕边动画
- POJ1275 Cashier Employment
- 求出data为首地址的100D字数组中的最小偶数,并把它存放在AX中,目前只能做出无符号数,有待修改
- Katana Op for visualization of OpenVDB
- 【心血之作】linux虚拟机下安装配置Hadoop(完全分布式)生态环境(hadoop2.2.0,HBase0.98,Hive0.13(连接oracle),sqoop1.4.4(连接oracle)
- 89978
- 实现投票,显示人数百分比的功能
- em,rem和vh
- 梯度弥散