A题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5640
官方题解:
显然这很像求最大公约数的过程嘛,看这张神图:
所以每次 gcd 的时候累加答案即可,复杂度 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
官方链接:
一个简单的模拟题,首先判断序列长度是否合法,接着判断 si 是否在 [1,9],最后扫一遍看看有没有重复以及跨过中间点的情况即可。
复杂度: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
官方题解:
数一个长度为 n 的序列 , 并且序列中不能出现长度大于 3 的连续的相同的字符 , 这玩意就是一个数位DP嘛。 定义 d[i][j][k] 为处理完 i 个字符 , 结尾字符为 ′a′+j , 结尾部分已重复出现了 k 次的方案数。 刷表转移一下就好啦。
复杂度:O(26∗26∗nT)
我的理解:递推f[n][i]代表长度为n末尾重复个数为i的数的个数,那么当n>=3时 f[n][1]=sum(f[n-1][1]+f[n-1][2]+f[n-1][3])*25 f[n][2]=f[n-1][2] f[n][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[n][0]);
}
return 0;
}
D题 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5643
官方题解:
约瑟夫问题的一个变种,然而题目全部是在唬人,就是一个简单的递推。虽然我知道有人会打表。。。
我们看看裸的约瑟夫是怎么玩的: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>
using namespace std;
const int N=5005;
int dp[N];
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[n]+=1;
printf("%d\n",dp[n]);
}
return 0;
}
E题 链接http://acm.hdu.edu.cn/showproblem.php?pid=5644
官方题解:
这是一个费用流建模题 , BC好久都没有费用流题了……
这个题关键是要满足每天的飞行员数量充足 , 那么可能是一开始就有的飞行员 , 或者某飞行员休假归来 , 或者新招募的飞行员。
将每一天拆成两个点 ,分别表示这一天开始时候飞行员的状态和这一天工作完成后飞行员的状态 , 分别为 Xi , Yi
先看看建图:
- 从 S 向每个 Xi 连一条容量为 Pi,费用为 0 的有向边。
- 从每个 Yi向 T 连一条容量为 Pi,费用为 0 的有向边。
- 从 S 向 Y1 连一条容量为 k , 费用为 0 的有向边。
- 从 S 向每个 Yi(i≥P) 连一条容量为无穷大,费用为 Q 的有向边。(新招募的)
- 从每个 Xi 向 Xi+1 连一条容量为无穷大,费用为 0 的有向边。
- 从每个Yi向Yi+1连一条容量为无穷大,费用为 0 的有向边。
- 对于每一个休假计划 , 从每一个Xi 向 Yi+Ti 连一条容量为无穷大费用为 Si 的有向边
然后开心的跑一跑最小费用最大流看一看是不是满流就可以判断是否可行啦……
我的思考:想是想不到,但是看到题解能理解,应该是网络流题目做太少了。最小费用最大流
#include<cstdio>
#include<cstring>
#include<queue>
#define inf 0x3f3f3f3f
using namespace std;
const int N=2005;
const int M=200005;
int dist[N],pre[N],head[N];
bool vis[N];
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[N];
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;
}