hdu 6071
題意:圖上有4個點,給定一個k求從2點出發最後回到2這個點且距離不小於k的最短距離。
思路:題目上說的從2出發最後又回到2,所以這是一個迴路,而兩個迴路可以形成一個新的迴路,假如現在有兩個迴路,長度分別爲a和b則可以形成新的迴路長度a+n*b。題目上讓求不小於k的最短路又是迴路,所以我們可以找到從2點出發的最小回路將它看做前邊提到的b則,我們只需要找到最小的a就可以找到最優解了,而b顯而易見的解是min(dis12,dis23)。但是這樣找什麼時候是終點呢,因爲迴路是無限的。但是我們可以想到假如前面的a%b的值相同那麼上面的解的值就是相同的,所以我們只需要找到%b的非負完全剩餘系裏面的最小值即可。
比如回到2點的時候有長度爲3的和長度爲5的兩條路徑,現在最短的迴路長度爲2,我們就可以在長度爲3的時候再走一遍最短迴路形成長度爲5的迴路,所以只需要記錄長度爲3就好了。
而我們就可以用最短路來實現,相當於將一個點拆分成了2*b個點,每個代表一個2*b的完全非負剩餘系的數。
#include<cstdio>
#include<queue>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
const int maxn=1e5+10;
const LL inf=1e17+8e16;
int head[maxn],cont,vis[5][maxn];
LL dis[5][maxn],mod;
/*
dis[i][j]數組存的是到達i點且長度對最短迴路取餘爲j的最短迴路長度
vis代表的和dis數組一樣但是是用來標記這種狀態是否在隊列中的
*/
struct zp
{
int u,v,next;
LL w;
}node[maxn];
void init()
{
cont=0;
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
}
void add(int u,int v,LL w)//雙向邊
{
node[cont].u=u,node[cont].v=v,node[cont].w=w;
node[cont].next=head[u];
head[u]=cont++;
node[cont].u=v,node[cont].v=u,node[cont].w=w;
node[cont].next=head[v];
head[v]=cont++;
}
struct edge
{
int id,dis;
edge(int _id,int _dis)
{
id=_id,dis=_dis;
}
};
void dij()//最短路算法
{
queue<edge> q;
q.push(edge(2,0));
dis[2][0]=0;
while(!q.empty())
{
int u=q.front().id;
int num=q.front().dis;
q.pop();
vis[u][num]=0;
for(int i=head[u];i!=-1;i=node[i].next)
{
int v=node[i].v;
LL w=node[i].w;
int tmp=(dis[u][num]+w)%mod;//到達v點餘數爲tmp
if(dis[v][tmp]>dis[u][num]+w)//更新最小值
{
dis[v][tmp]=dis[u][num]+w;
if(!vis[v][tmp])//標記
{
q.push(edge(v,tmp));
vis[v][tmp]=1;
}
}
}
}
}
int main()
{
int ncase;
scanf("%d",&ncase);
while(ncase--)
{
init();
LL k,a,b,c,d;
scanf("%lld%lld%lld%lld%lld",&k,&a,&b,&c,&d);
add(1,2,a),add(2,3,b),add(3,4,c),add(4,1,d);
mod=2*min(a,b);//最短迴路
for(int i=1;i<=4;i++)
for(int j=0;j<mod;j++)
dis[i][j]=inf;
dij();
LL ans=((k-1)/mod+1)*mod;//直接走最短迴路的解
for(int i=0;i<mod;i++)//枚舉餘數找最優解
{
if(dis[2][i]>=k) ans=min(ans,dis[2][i]);
else ans=min(ans,((k-dis[2][i]-1)/mod+1)*mod+dis[2][i]);
}
printf("%lld\n",ans);
}
}
bnu oj商品裝箱
這裏有一個類似的題目和解法
思路:和上一道題同樣的思想,這一是一個無限的構造,每個數都可以用無數次,但是對其中某個數a取餘確實有限的,只要餘數相同就可大的數就可以用最小的數構造出來所以我們可以求出關於一個數的所有餘數的最小花費,及到達某個餘數構造出來的最小數,這就可以根據餘數之間的關係來建立最短路,路上的花費就是這個數,兩端的節點就是不同的餘數。
這樣跑一邊最短路就可以知道有哪些餘數可以達到,有哪些不可以達到。
#include<cstdio>
#include<cstring>
#include<queue>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1e6+10;
const int inf=0x3f3f3f3f;
int x[maxn];
struct zp
{
int u,v,w,Next;
} node[maxn];
int head[maxn],cont;
void init()
{
memset(head,-1,sizeof(head));
cont=0;
}
void add(int u,int v,int w)
{
node[cont].u=u,node[cont].v=v,node[cont].w=w;
node[cont].Next=head[u];
head[u]=cont++;
}
int SPFA()
{
int n=x[0];
int dis[10010],vis[10010];
memset(dis,0x3f3f,sizeof(dis));
memset(vis,0,sizeof(vis));
queue<int> q;
q.push(0);
dis[0]=0;
vis[0]=1;
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i!=-1;i=node[i].Next)
{
int v=node[i].v,w=node[i].w;
if(dis[u]+w<dis[v])
{
dis[v]=dis[u]+w;
if(vis[v]==0)
q.push(v);
}
}
}
int Max=-1;
for(int i=0;i<n;i++)
Max=max(Max,dis[i]);
if(Max==0||Max==inf) return -1;//如果有不可達到的餘數就不可能有最小值
else return Max-n+1;//返回最小值,代表最小值後面的都可以被構造出來
}
int solve(int n)
{
int z=x[0];//選最小的數
int b[10010],a[10010];
int tot=0;
memset(b,0,sizeof(b));
for(int i=0;i<n;i++)
{
int tmp=x[i]%z;
if(!b[tmp])//將餘數不同的記錄起來
{
a[tot++]=x[i];
b[tmp]=1;
}
}
for(int i=0;i<z;i++)//建圖
{
for(int j=0;j<tot;j++)//根據餘數之間的關係
{
if(i!=(i+a[j])%z)//餘數i可以通過+a[j]來到達餘數tmp
{
int tmp=(i+a[j])%z;
add(i,tmp,a[j]);
}
}
}
return SPFA();
}
int main()
{
int ncase;
scanf("%d",&ncase);
while(ncase--)
{
init();
int n;
scanf("%d",&n);
for(int i=0; i<n; i++)
scanf("%d",&x[i]);
sort(x,x+n);
if(x[0]==1)
printf("1\n");
else
printf("%d\n",solve(n));
}
}