箬蒻终于要动笔了。
第四章写的很慢因为,区间DP实在玩不转,好好加油⑧。
-
A - Grazing on the Run
题意:http://poj.org/problem?id=3042
可以想到当前吃掉的草一定是一个区间(因为经过的草一定会吃掉),然后最后一定会停在左端点或者右端点。
dp[ i ][ j ][0/1]表示已经吃了[ i , j ]的草,最后停在左/右端点时草的总腐败量,
在最左侧要到达i点可以通过i+1点或j点转移,在最右侧类似
转移到i时,除了即将到达的i点,还有未到达的(n-(j-i+1))个点,
即总共(n-(j-i))个点,它们的枯萎指数均在增加。
ps:做这题也发现memset一个赋最大值应用,但是还不是太懂为什么,先码着
memset(a,127,sizeof(a)),即得到无穷大。memset(a,128,sizeof(a)),即得到无穷小,与上述的值互为相反数。
对于a数组为int或long long时,成立。
#include<algorithm> using namespace std; typedef long long ll; const int MAXN=1e5+10; const int INF=0x3f3f3f; #define endll printf("\n") #define s1(a) scanf("%d",&a) #define s2(a,b) scanf("%d %d",&a,&b) #define mem(a,b) memset(a,b,sizeof(a)) #define s3(a,b,c) scanf("%d %d %d",&a,&b,&c) int p[1111]; int dp[1111][1111][2]; int main() { int n,l; while(~s2(n,l)) { for(int i=1;i<=n;s1(p[i++])); sort(p+1,p+n+1); mem(dp,127); for(int i=1;i<=n;i++) dp[i][i][0]=dp[i][i][1]=abs(l-p[i])*n;//走到i点过程中有n个点正在腐化 for(int len=2;len<=n;len++) { for(int i=1;i<=n-len+1;i++) { int j=i+len-1; dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][0]+(n-(j-i))*abs(p[i+1]-p[i])); dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][1]+(n-(j-i))*abs(p[j]-p[i])); dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][1]+(n-(j-i))*abs(p[j]-p[j-1])); dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][0]+(n-(j-i))*abs(p[j]-p[i])); } } printf("%d\n",min(dp[1][n][0],dp[1][n][1])); }; return 0; }
-
E - Cow Relays
题意:http://poj.org/problem?id=3613
就是求S到E经过K个边的最短路啦)
主要用到的就是离散数学里的矩阵幂乘求可达性矩阵(结合Floyd思想)以及快速幂思想
一行Floyd算法:
c[i][j]=min(c[i][j],a[i][k]+b[k][j]);
求出的新矩阵c,矩阵里每条边都相当于从a中取出一条边(i,k),从b中取出一条边(k,j)组成新边,与矩阵幂乘相同,
求出的c中即为原图中每个点经过两条边到达其他点的最小距离,要求经过K条边的最短路,做K-1次矩阵幂乘即可。
同时,如果每次都和原图矩阵相乘太费时间,采用快速幂的思想,将K拆为1+x(K为奇数时)和x+x。
同时发现和A题相同有memset赋最值的应用,但是不知为何赋值127会WA。。。以后还是赋0x3f保险吧。。。
AC代码:
#include<algorithm> using namespace std; typedef long long ll; const int MAXN=1e5+10; const int INF=0x3f3f3f; #define endll printf("\n") #define s1(a) scanf("%d",&a) #define s2(a,b) scanf("%d %d",&a,&b) #define mem(a,b) memset(a,b,sizeof(a)) #define s3(a,b,c) scanf("%d %d %d",&a,&b,&c) int K,T,S,E,n; int mp[1111]; struct IN { int a[222][222]; IN operator * (IN &b) { IN c; mem(c.a,0x3f); for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) c.a[i][j]=min(c.a[i][j],a[i][k]+b.a[k][j]); return c; } }st,ans; void solve() { ans=st; K--; while(K) { if(K&1) ans=ans*st; st=st*st; K>>=1; } } int main() { n=0; mem(st.a,0x3f);mem(mp,0); scanf("%d %d %d %d",&K,&T,&S,&E); for(int i=0,u,v,w;i<T;i++) { s3(w,u,v); if(!mp[u]) mp[u]=++n; if(!mp[v]) mp[v]=++n; st.a[mp[u]][mp[v]]=st.a[mp[v]][mp[u]]=w; } solve(); printf("%d\n",ans.a[mp[S]][mp[E]]); return 0; }
-
K - Moogle
题意:http://poj.org/problem?id=3638
给你一个地图,有h个点但是只能保存c个点,未保存的点按照线性关系推算,问你这种推算方式最小误差是多少
又是DP。。dp[ i ][ j ]表示到i位置保存j 个房子的最小误差,
那么每个状态可由前面的状态推出(前面保存了j-1个房子的状态的dp值+前面状态末位置到现位置的总误差)
#include<cmath>#include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; typedef long long ll; const int MAXN=1e5+10; const int INF=0x3f3f3f; #define s1(a) scanf("%d",&a) #define s2(a,b) scanf("%d %d",&a,&b) #define mem(a,b) memset(a,b,sizeof(a))double p[222]; double d[222][222];//只确定i,j,中间产生的总误差 double dp[222][222]; int main() { int t;s1(t); while(t--) { int h,c; s2(h,c); mem(d,0); for(int i=1;i<=h;i++) scanf("%lf",&p[i]); for(int i=1;i<=h;i++) { for(int j=i+1;j<=h;j++) { double temp=(p[j]-p[i])/(j-i); for(int k=i+1;k<j;k++) d[i][j]+=fabs(p[i]+temp*(k-i)-p[k]); } } //dp[i][j]表示在第i位置确定j个house mem(dp,0x43); for(int i=2;i<=h;i++) { dp[i][2]=d[1][i]; for(int j=3;j<=c&&j<=i;j++) { for(int k=j-1;k<i;k++)//保存j-1个房子最少需要到j-1位置 dp[i][j]=min(dp[i][j],dp[k][j-1]+d[k][i]); } } printf("%.4lf\n",dp[h][c]/h); } return 0; }
-
J - Evacuation
朕乏了
题意:http://poj.org/problem?id=3057
每个人不能同时出出口,但是可以走在同一块空地上
问你最小获救时间
#include<cmath> #include<cstdio> #include<vector> #include<string> #include<cstdlib> #include<cstring> #include<cassert> #include<iostream> #include<algorithm> using namespace std; typedef long long ll; const int MAXN=1e5+10; const int INF=0x3f3f3f; #define s1(a) scanf("%d",&a) #define s2(a,b) scanf("%d %d",&a,&b) #define mem(a,b) memset(a,b,sizeof(a)) //二分图最大匹配匈牙利算法 //bfs预处理 int X,Y,n; char mp[20][20]; //匈牙利 int vis[55555]; int match[55555]; vector<int>G[55555]; //建图 vector<int>dx,dy;//门的坐标 vector<int>px,py;//人的坐标 int dxx[5]={0,1,0,-1}; int dyy[5]={1,0,-1,0}; int dis[20][20][20][20];//前两维代表门后两维代表人void bfs(int x,int y,int d[20][20])//求出各点到每个门的距离 { queue<int>qx,qy; d[x][y]=0; qx.push(x); qy.push(y); while(!qx.empty()) { x=qx.front();qx.pop(); y=qy.front();qy.pop(); for(int i=0;i<4;i++) { int nx=x+dxx[i],ny=y+dyy[i]; if(nx>=0&&nx<X&&ny>=0&&ny<Y&&mp[nx][ny]=='.'&&d[nx][ny]<0) { d[nx][ny]=d[x][y]+1; qx.push(nx); qy.push(ny); } } } } void input()//输入加预处理 { s2(X,Y); dx.clear();dy.clear(); px.clear();py.clear(); mem(dis,-1); for(int i=0;i<X;i++) scanf("%s",mp[i]); for(int i=0;i<X;i++) { for(int j=0;j<Y;j++) { if(mp[i][j]=='D') { dx.push_back(i); dy.push_back(j); bfs(i,j,dis[i][j]);//相当于将四维数组的后两维传入函数} else if(mp[i][j]=='.') { px.push_back(i); py.push_back(j); } } } } int dfs(int v) { vis[v]=1; int len=G[v].size(); for(int i=0;i<len;i++) { int u=G[v][i]; if(match[u]==-1||(!vis[match[u]]&&dfs(match[u]))) { match[v]=u; match[u]=v; return 1; } } return 0; } int main() { int t;s1(t); while(t--) { input(); //建图 n=X*Y;//时间上限 int d=dx.size(),p=px.size(); for(int i=0;i<n*d+p;i++) G[i].clear(); for(int i=0;i<d;i++) { for(int j=0;j<p;j++) { if(dis[dx[i]][dy[i]][px[j]][py[j]]>=0) { for(int k=dis[dx[i]][dy[i]][px[j]][py[j]];k<=n;k++)//将时间和门和人建边 { int u=(k-1)*d+i,v=n*d+j; //u相当于时间和门组成的二元组,i最大值就是d,所以乘d后加i(状压 //v即为此时的人, //加n*d为了防止人编号和状压后的数字重合((k-1)*d+i小于n*d,所以n*d+j绝对大于(k-1)*d+i //菊苣是怎么想到这样压缩状态的我哭了 G[u].push_back(v); G[v].push_back(u);//二元组建边 } } } } //匈牙利找增广路 int flag=0; if(p==0) { printf("0\n"); continue; } int num=0; mem(match,-1); for(int i=0;i<n*d;i++) { mem(vis,0); if(dfs(i)) { if(++num==p)//当匹配数到达总人数就说明可以在时间t内全部逃出了 { printf("%d\n",i/d+1); flag=1; break; } } } if(flag==0) printf("impossible\n"); } return 0; }
-
F - Big Christmas Tree
题意:http://poj.org/problem?id=3013
给你每个点的权重和一些边,求以1点为根节点的最小树(边的价值之和最小)
圣诞树的边 u-v 的价值等于边 u-v 的权值*(v为根节点的子树中的所有节点的权重和)
超像最小生成树对吧,,,但是写到一半你会发现,这个价值计算方法,在得知最小树之前,是不能先算出每个边的价值的。。。
然后,
其实对于一个节点,它在走到根节点所经过的每一条路,都需要乘上这个节点的权值。因为这个节点是它所路过节点的子节点,所以所有路过的边都会成上这个节点的权值。因为一个节点的权值是定值。
所以最后转换成求每个点到1点的最短路问题,,SPFA
n=0或者1, 答案是0,V=1的时候,只有1一个节点,不需要任何边去构成
还有就是INF尽量大一些。
//#include <bits/stdc++.h> #include<map> #include<set> #include<queue> #include<cmath> #include<stack> #include<ctime> #include<cstdio> #include<vector> #include<string> #include<sstream> #include<cstdlib> #include<cstring> #include<cassert> #include<iostream> #include<algorithm> using namespace std; typedef long long ll; const int MAXN=5e4+10; #define pi acos(-1.0) #define INF 0x3f3f3f3f3f #define mod 1000000009 #define endll printf("\n") #define s1(a) scanf("%d",&a) #define s2(a,b) scanf("%d %d",&a,&b) #define mem(a,b) memset(a,b,sizeof(a)) #define s3(a,b,c) scanf("%d %d %d",&a,&b,&c)int n,m,tot; int head[MAXN]; int w[MAXN]; ll dis[MAXN]; int vis[MAXN]; struct IN { int v; ll val; int next; }edge[MAXN<<2];//注意无向存图 void addedge(int u,int v,ll val) { edge[tot].v=v; edge[tot].val=val; edge[tot].next=head[u]; head[u]=tot++; } void input() { s2(n,m); tot=1; mem(head,0); for(int i=1;i<=n;i++) { vis[i]=0; dis[i]=INF; } for(int i=1;i<=n;i++) s1(w[i]); for(int i=0;i<m;i++) { int u,v; ll val; scanf("%d %d %lld",&u,&v,&val); addedge(u,v,val); addedge(v,u,val); } } void spfa_bfs(int x)//x=start { queue<int>q; q.push(x); vis[x]=1;dis[x]=0; while(!q.empty()) { x=q.front();q.pop(); vis[x]=0; for(int i=head[x];i;i=edge[i].next) { int nx=edge[i].v; if(dis[nx]>dis[x]+edge[i].val) { dis[nx]=dis[x]+edge[i].val; if(!vis[nx]) { vis[nx]=1; q.push(nx); } } } } } int main() { int t;s1(t); while(t--) { input(); if(n==0||n==1) printf("0\n"); else { spfa_bfs(1); ll ans=0; int flag=1; for(int i=1;i<=n;i++) { if(dis[i]<INF) ans+=w[i]*dis[i]; else { printf("No Answer\n"); flag=0; break; } } if(flag) printf("%lld\n",ans); } } return 0; }