T1
不是很难,注意积雪高度的判断(要开long long
)以及终点不需要特判即可。
#include<cstdio>
#include<cstring>
const int maxn=1e5+2;
struct Solution
{
struct Edge{int from,to,len;};
struct Graph
{
Edge edges[maxn*10];
int edge_cnt,Head[maxn],Next[maxn*10];
void add_edge(int from,int to,int len)
{
edges[++edge_cnt]=(Edge){from,to,len};
Next[edge_cnt]=Head[from],Head[from]=edge_cnt;return;
}
}G;
int bh[maxn],lh[maxn],ad;
long long dis[maxn],vis[maxn];
int S,T;
int Q[maxn*10];
void SPFA()
{
memset(dis,0x3f,sizeof(dis));
dis[S]=0;int h=0,t=0;Q[t++]=S;
while( h!=t )
{
int p=Q[h++];h%=maxn*5;
vis[p]=0;
for(int i=G.Head[p];i;i=G.Next[i])
{
Edge e=G.edges[i];
if( ( e.to==T or bh[e.to]+ad*( (long long)dis[p]+e.len )<=lh[e.to] ) and dis[p]+(long long)e.len<dis[e.to] )
{
dis[e.to]=dis[p]+e.len;
if( !vis[e.to] ) vis[e.to]=1,Q[t++]=e.to,t%=maxn*5;
}
}
}
return;
}
void solve()
{
int n,m,tl;scanf("%d%d%d%d%d%d",&n,&m,&S,&T,&tl,&ad);
for(int i=1;i<=n;i++) scanf("%d%d",&bh[i],&lh[i]);
for(int i=1;i<=m;i++)
{
int x,y,l;scanf("%d%d%d",&x,&y,&l);
G.add_edge(x,y,l),G.add_edge(y,x,l);
}
SPFA();
if( dis[T]<=tl ) printf("%d",dis[T]);
else printf("wtnap wa kotori no oyatsu desu!");
return;
}
}SOL;
int main()
{
SOL.solve();return 0;
}
T2
这个有点意思。
最短距离不难求,但是怎么求方案数呢?
if( dis[s][i]+dis[i][t]==dis[s][t] ) ans[s][t]+=ans[s][i]*ans[i][t];
上面的式子不难理解,但是实际题目中的写法必须是下面这样:
for(int i=1;i<=n;i++)
for(int s=1;s<=n;s++)
for(int t=1;t<=n;t++)
if( s!=i and i!=t and s!=t )
{
if( dis[s][i]+dis[i][t]<dis[s][t] )
{
dis[s][t]=dis[s][i]+dis[i][t];
ans[s][t]=ans[s][i]*ans[i][t];
}
else if( dis[s][i]+dis[i][t]==dis[s][t] ) ans[s][t]+=ans[s][i]*ans[i][t];
}
也就是说,我们要边求最短路边统计方案数。
为什么要这样呢?难道我不可以求完最短路再求方案数吗?
不可以。
上面的例子,算完最短路之后,就会有两种“可行”的方案:
- (1-2) (2-3-4)
- (1-2-3) (3-4)
问题在于:对于有多个节点的最短路径,我们要找到表示它的唯一方法。
这也就是为什么要在求最短路的时候同时求方案数。
假设现在首先以(2)为中继点,那么我们就只能使(1 o 3)的方案数等于(1),至于(2 o 4)我们要不已经维护过了,要不就是维护不了的(因为不能把路径分解为(2 o 2)和(2 o 4),图上没有自环,而且当前(2 o 4)还没有被维护到)
然后,再枚举到以(3)为中继点的时候,就可以用(1 o 3)和(3 o 4)来表示(1 o 4)这条路径了。
(不用担心重复,重复的话意味着(1 o 2)和(2 o 4)也被统计了,但是这里以(2)为中继点的时候,我们还没有维护(2 o 4)啊,而且维护完(2 o 4)的时候我们也不会再去枚举回(2)了,所以不会重复)
扯了这么多,就是为了说明:要在求最短路的同时求方案数。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=102,maxm=9002;
struct Solution
{
int dis[maxn][maxn];
long long ans[maxn][maxn];
void solve()
{
memset(dis,0x3f,sizeof(dis));
int n,m;scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y,l;scanf("%d%d%d",&x,&y,&l);
dis[x][y]=l,ans[x][y]=1;
dis[y][x]=l,ans[y][x]=1;
}
for(int i=1;i<=n;i++)
for(int s=1;s<=n;s++)
for(int t=1;t<=n;t++)
if( s!=i and i!=t and s!=t )
{
if( dis[s][i]+dis[i][t]<dis[s][t] )
{
dis[s][t]=dis[s][i]+dis[i][t];
ans[s][t]=ans[s][i]*ans[i][t];
}
else if( dis[s][i]+dis[i][t]==dis[s][t] ) ans[s][t]+=ans[s][i]*ans[i][t];
}
for(int i=1;i<=n;i++)
{
double tmp=0.0;
for(int s=1;s<=n;s++)
{
for(int t=1;t<=n;t++)
{
if( s==i or i==t or s==t ) continue;
if( dis[s][i]+dis[i][t]==dis[s][t] ) tmp+=( (double)ans[s][i]*ans[i][t] )/ans[s][t];
}
}
printf("%.3lf
",tmp);
}
return;
}
}SOL;
int main()
{
SOL.solve();return 0;
}
T3
其实理解成矩阵就很好理解了:
(A)矩阵和(B)矩阵分别表示经过(x)条边和(y)条边的矩阵,那么(C=A imes B)就是经过(x+y)条边的矩阵。
JZ res;
for(int i=1;i<=idx_cnt;i++)
for(int s=1;s<=idx_cnt;s++)
for(int t=1;t<=idx_cnt;t++)
res.a[s][t]=std::min( res.a[s][t],a[s][i]+op.a[i][t] );
return res;
和普通的(Floyd)算法不同的是,在这里,等式两边的矩阵是相互独立的。也就是说,(C)矩阵中的(dis)不会对(A)和(B)当中的产生影响,所以(C)矩阵是切切实实只表示(x+y)条边的矩阵。
而(Floyd)算法中,用(1)条边的信息统计完(2)条边的信息之后,(2)条边的信息又要马上在(dis)数组里面用来统计(3)条、(4)条边的信息,所以不是独立的。
知道具体含义之后就可以矩阵快速幂了:
#include<cstdio>
#include<cstring>
#include<algorithm>
int idx[1002],idx_cnt;
struct JZ
{
int a[105][105];
JZ(){memset(a,0x3f,sizeof(a));}
JZ operator * (const JZ &op)
{
JZ res;
for(int i=1;i<=idx_cnt;i++)
for(int s=1;s<=idx_cnt;s++)
for(int t=1;t<=idx_cnt;t++)
res.a[s][t]=std::min( res.a[s][t],a[s][i]+op.a[i][t] );
return res;
}
JZ operator *= (const JZ &op)
{
*this = (*this)*op;
return *this;
}
};
int main()
{
JZ dis;
int K,m,S,T;scanf("%d%d%d%d",&K,&m,&S,&T);
for(int i=1;i<=m;i++)
{
int len,x,y;scanf("%d%d%d",&len,&x,&y);
if( !idx[x] ) idx[x]=++idx_cnt;
if( !idx[y] ) idx[y]=++idx_cnt;
dis.a[idx[x]][idx[y]]=dis.a[idx[y]][idx[x]]=len;
}
JZ ans=dis;--K;
while(K)
{
if( K&1 ) ans*=dis;
dis*=dis;K>>=1;
}
printf("%d",ans.a[idx[S]][idx[T]]);
return 0;
}
T4
最短路+计算几何是真的神奇。
首先,那些所在直线会穿过被保护节点(把它们分成两部分)的线段是不能建边的(要不就是真的穿过了,要不就是没有穿过,但是方向不对,选了之后会多绕一条线段)
然后就要单向建边。
例如,如果对于(overrightarrow{AB}),所有被保护节点都在它的右边(也可以与它共线),那么就可以直接从(A)连一条边到(B)。
其实这样做是为了求有向图最小环(无向图的太难了,偷懒嘛)。
有向图最小环就很简单,直接(Floyd)之后,(dis(i,i))就是经过(i)点的最小环的长度。
无向图最小环另外讲。
#include<cstdio>
#include<cstring>
#include<algorithm>
const int maxn=502;
struct Solution
{
struct Point
{
int x,y;
Point(int x=0,int y=0){this->x=x,this->y=y;}
void read(){scanf("%d%d",&x,&y);return;}
};
typedef Point Vector;
Vector make_vec(Point s,Point t){return Vector(t.x-s.x,t.y-s.y);}
int Cross(Vector A,Vector B){return A.x*B.y-A.y*B.x;}
Point P1[maxn],P2[maxn];
int dis[maxn][maxn];
bool solve()
{
memset(dis,0x3f,sizeof(dis));
int n1;if( scanf("%d",&n1)==EOF ) return 0;
for(int i=1;i<=n1;i++) P1[i].read();
int n2;scanf("%d",&n2);
for(int i=1;i<=n2;i++) P2[i].read();
for(int s=1;s<=n2;s++)
for(int t=1;t<=n2;t++)
{
if( s==t ) continue;
bool flag=1;
for(int k=1;k<=n1;k++)
if( Cross( make_vec( P2[s],P2[t] ),make_vec( P2[s],P1[k] ) )>0 ){flag=0;break;}
if(flag) dis[s][t]=1;
}
for(int i=1;i<=n2;i++)
for(int s=1;s<=n2;s++)
{
if( dis[s][i]==0x3f3f3f3f ) continue;
for(int t=1;t<=n2;t++)
dis[s][t]=std::min( dis[s][t],dis[s][i]+dis[i][t] );
}
int ans=0x3f3f3f3f;
for(int i=1;i<=n2;i++) ans=std::min( ans,dis[i][i] );;
if( ans>n2 ) puts("ToT");
else printf("%d
",n2-ans);
return 1;
}
}SOL;
int main()
{
while( SOL.solve() );return 0;
}