First
tag:图论,最大流,模板题
题面
题目描述
同一时刻有N位车主带着他们的爱车来到了汽车维修中心。维修中心共有M位技术人员,不同的技术人员对不同的车进行维修所用的时间是不同的。现在需要安排这M位技术人员所维修的车及顺序,使得顾客平均等待的时间最小。 说明:顾客的等待时间是指从他把车送至维修中心到维修完毕所用的时间。
输入
第一行有两个m,n,表示技术人员数与顾客数。 接下来n行,每行m个整数。第i+1行第j个数表示第j位技术人员维修第i辆车需要用的时间T。
输出
最小平均等待时间,答案精确到小数点后2位。
样例输入
2 2
3 2
1 4
样例输出
1.50
提示
数据范围: (2<=M<=9,1<=N<=60)
思路
题解
第一感觉,是用DP做,用DP想了半天发觉不对。搜了题解才知道,是用最大流的最小费用做。 地地道道的一道模板题,在网上搜到了模板。最主要的是:如何建图。首先建立源点S和汇点T,N辆车和M个工人都作为中间的节点,其中每位工人分为N个,代表服务车的顺序。将S与N辆车相连,N*M个工人节点与T相连,流均为1,费用为0,再将N辆车与N*M个工人节点相连,流也为1,费用计算方法为:k*t[i][j],即第i辆车由第j个工人修理的时间*后面需要的车辆数(意思是:假如第j个工人修理第i辆车的次序为x,那么还有(n-x)辆车也需要等待t[i][j]的时间)。建图完毕后,调用模板进行计算即可得出总的等待时间,除以n就可以平均等待时间。
源码
#include <iostream>
#include <queue>
#include <cstdio>
#include <cstring>
#define inf 0x7fffffff
#define mem(a,b) memset(a,b,sizeof(a))
#define T 1001
using namespace std;
const int MAXN=1e6+5;
int n,m,ans,cnt=0,st,ed,t[65][10];
int head,tail;
int list[1005],pre[1005],vi[1005],first[1005],c[1005];
bool v[1005];
typedef struct {//定义节点类型
int from,to,next,f,w,other;
}Node;
Node e[MAXN];
void insert(int u,int v,int f,int w){//插入节点(包括正向节点和反向节点)
int kx,ky;
cnt++;
kx=cnt;
e[cnt].from=u;e[cnt].to=v;e[cnt].f=f;e[cnt].w=w;
e[cnt].next=first[u];first[u]=cnt;
cnt++;ky=cnt;
e[cnt].from=v;e[cnt].to=u;e[cnt].f=0;e[cnt].w=-w;
e[cnt].next=first[v];first[v]=cnt;
e[kx].other=ky;e[ky].other=kx;
}
bool spfa(){//求解算法
mem(c,0);c[st]=999999999;
//memset(v,false,sizeof(v));
mem(v,false);
v[st]=true;
mem(vi,0x3F);vi[st]=0;
list[1]=st;head=1;tail=2;pre[st]=0;
while(head!=tail){
int x=list[head];
for(int k=first[x];k>0;k=e[k].next){
int y=e[k].to;
if(e[k].f>0&&vi[y]>vi[x]+e[k].w){
vi[y]=vi[x]+e[k].w;
c[y]=min(c[x],e[k].f);
pre[y]=k;
if(v[y]==false){
v[y]=true;
list[tail++]=y;
if(tail==ed+1)tail=1;
}
}
}
head++;if(head==ed+1)head=1;
v[x]=false;
}
if(vi[ed]>999999999)return false;
ans+=vi[ed]*c[ed];
int x=ed;
while(x!=st){
int k=pre[x];
e[k].f-=c[ed];e[e[k].other].f+=c[ed];
x=e[k].from;
}
return true;
}
int main(){
scanf("%d%d",&m,&n);
st=n*m+n+1;ed=n*m+n+2;
cnt=0;mem(first,0);
int i,j,k,p;
for(i=1;i<=n;i++)
for( j=1;j<=m;j++){
scanf("%d",&t[i][j]);//输入第i辆车由第j个工人修理所需时间
}
for(i=1;i<=n;i++) insert(st,i,1,0);//将源点S和N个车节点相连
p=m*n;
for(j=1;j<=p;j++) insert(n+j,ed,1,0);//将N*M个工人节点与汇点T相连
for(i=1;i<=n;i++){
for(j=1;j<=m;j++){
for(k=1;k<=n;k++){
insert(i,j*n+k,1,t[i][j]*(n-k+1));//将N个车节点与N*M个工人节点相连
}
}
}
ans=0;
while(spfa());//求解
printf("%.2lf\n",double(ans)/double(n));
return 0;
}
Second
tag:数论,莫比乌斯反演
题面
题目描述
FGD正在破解一段密码,他需要回答很多类似的问题:对于给定的整数a,b和d,有多少正整数对x,y,满足x<=a,y<=b,并且gcd(x,y)=d。作为FGD的同学,FGD希望得到你的帮助。
输入
第一行包含一个正整数n,表示一共有n组询问。(1<=n<= 50000)接下来n行,每行表示一个询问,每行三个正整数,分别为a,b,d。(1<=d<=a,b<=50000)
输出
对于每组询问,输出到输出文件zap.out一个正整数,表示满足条件的整数对数。
样例输入
2
4 5 2
6 4 3
样例输出
3
2
提示
对于第一组询问,满足条件的整数对有(2,2),(2,4),(4,2)。对于第二组询问,满足条件的整数对有(6,3),(3,3)。
思路
题解
想要暴力做,来着,结果…… 一道数论题,运用莫比乌斯反演知识,推导。
ans=
Σ
\Sigma
Σ[gcd(x, y)=d] (1<=x<= a,1<=y<=b),
令a’=a/d,b’=b/d,化简一下得到:
ans =
Σ
\Sigma
Σμ(t)*⌊a’/t⌋*⌊b’/t⌋
⌊a’/t⌋相等的是一段连续的区间, ⌊b’/t⌋同理。
然后求μ的前缀和,再进行分块处理。
源码
#include <iostream>
#include <cstring>
#include <algorithm>
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int N=1e5;
ll mu[N],sum[N],prime[N],num,n,m,t,d,ans,net;
bool pri[N];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
mem(mu,0);mem(pri,false);
num=0;n=50000; mu[1]=1; pri[1]= true;
//核心部分,莫比乌斯反演
for(int i=2;i<=n;i++)
{
if(!pri[i])
{
prime[++num]=i;
mu[i]=-1;
}
for(int j=1;j<=num&&i*prime[j]<=n;j++)
{
pri[i*prime[j]]=true;
mu[i*prime[j]]=-mu[i];
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
}
}
//
for(int i=1;i<=n;i++) sum[i]=sum[i-1]+mu[i];
//开始
cin>>t;
while(t--)
{
cin>>n>>m>>d;ans=0;
if(n>m) swap(n,m);
n/=d; m/=d;
for(ll i=1;i<=n;i=net)
{
net=min(n/(n/i),m/(m/i))+1;
ans+=(sum[net-1]-sum[i-1])*(n/i)*(m/i);
}
cout<<ans<<"\n";
}
return 0;
}