【题目大意】
某地的机房要集体女装,但是教练不想让他们女装,放松了限制,但出题人想至少要有一个人女装,求减去的最大限制使得至少有一个人女装。
咳咳,下面才是正题:
【解题思路】
1 . 总体思路
因为题目中说出了 不小于 ,说明要列不等式,可要用差分约束来解决。
本题要求出最大值 \(T\) 来满足有人女装,直接求 \(T\) 肯定不好求,又因为要求出最大值,考虑二分来解决。
根据题目要求列出不等式,每次分出一个 \(T\) 值,根据这个 \(T\) 值进行连边,跑一遍最长路,如果存在环路就说明合法,可以增加减去的 \(T\) 值,反之减少,最后得出答案。
2 . 条件分析
根据题目,有两种情况 :
- 我没 \(k\) 倍杀选手 \(X\),我就女装
-
选手 \(Y\) 把我 \(k\) 倍杀我就女装
所以在教练更改以后同学 \(A\) 要女装的条件分别为 :
根据上面两个式子可以根据差分约束来连边。
- 第一种情况从 \(B\) 向 \(A\) 连一条边权为 \(k - T\) 的边。
- 第二种情况从 \(B\) 向 \(A\) 连一条边权为 $ \dfrac{1}{k + T}$ 的边。
要保证图联通,所以建立一个超级源点 \(0\) ,要保证根据已有同学的成绩来作为判断的条件,所以要根据已有同学的成绩连边,连边时注意要两种情况都连边。
例如当知道 \(C\) 的成绩为 \(10\) 时,从 \(0\) 向 \(C\) 建立一条边权为 \(10\) 的边。还要从 \(C\) 向 \(0\) 建立一条边权为 \(\dfrac{1}{10}\) 的边。
这时候基本上都分析完了, 开始二分建图求最大 \(T\)。
3 . 二分建图
二分不用说了吧... 注意精度开大点即可。
4 . 我的错误
被 \(n\) 个错误卡炸的我决定诉说一下
- 写二分精度的时候 \(EPS\) 要定义为 \(double\) ,否则一直死循环。
- 因为是乘积最短路,\(dis\) 数组要附初值为 \(1\);
- 千万别用 \(memset\) 附初值为 \(1\),就我傻傻的调了半天也没看出来。
- 多组数据记着清空;
这些错误也就卡了我半天...
【Code】
/*
By : Ti_Despairy
Time : 2021,1,21;
思路 :
知识点 :
*/
#include<cstdio>
#include<queue>
#include<iostream>
#include<cstring>
#define M 1000010
#define N 1010
#define qaq cout<<"可行QAQ"<<endl
#define INF 0x3f3f3f3f
using namespace std;
const int mod1 = 19260817;
const int mod2 = 19660813;
const double EPS = 1e-7;//确保精度
/*================================================*/
int n,m,cnt,t;
struct node{
int next;
int to;
double dis;
}e[N << 1];
struct nod{
int pos;
int u;
int v;
double val;
}bian[N << 1];
int head[N];
double w[N];
int vis[N];
int in[N];//入队次数
int id[N];
/*================================================*/
inline int read()
{
int s = 0, f = 0;char ch = getchar();
while (!isdigit(ch)) f |= ch == '-', ch = getchar();
while (isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar();
return f ? -s : s;
}
void add(int from,int to,double dis)
{
e[++ cnt].to = to;
e[cnt].dis = dis;
e[cnt].next = head[from];
head[from] = cnt;
return;
}
bool check(double T)
{
queue <int> qp;
while(qp.size()) qp.pop();
for(int i = 1;i <= n;i ++) w[i] = 1;
// 有时候当你一直输出 0 时 ,看看这个 。。。
memset(head,0,sizeof(head));
memset(vis,0,sizeof(vis));
memset(in,0,sizeof(in));
cnt = 0;
// 数据清空
for(int i = 1;i <= m;i ++) {//连边
if(id[bian[i].u] && id[bian[i].v] && ((bian[i].pos == 1 && id[bian[i].u] < id[bian[i].v] * (bian[i].val - T))
|| (bian[i].pos == 2 && id[bian[i].u] < id[bian[i].v] / (bian[i].val + T )))) return true; //剪枝
if(bian[i].pos == 1) add(bian[i].v ,bian[i].u ,bian[i].val - T);
// x < y * (k - T) ;
else add(bian[i].v ,bian[i].u ,1.0 / (bian[i].val + T));
// x < y * 1 / (k + T);
}
for(int i = 1;i <= t;i ++) {
// 由已知数据来建立超级源点, 所有的情况建立在这之上
if(id[i]) {
add(0,i,id[i]);
add(i,0,1.0 / id[i]);
}
}
w[0] = 1;
vis[0] = 1;
in[0] = 1;//入队次数
qp.push(0);
while(!qp.empty()) {
int x = qp.front();
qp.pop();
vis[x] = false;
for(int i = head[x];i; i = e[i].next) {
int y = e[i].to;
if(w[y] < w[x] * e[i].dis) {
w[y] = w[x] * e[i].dis;
if(!vis[y]) {
vis[y] = true;
qp.push(y);
++ in[y];
if(in[y] >= n + 1) return true;
}
}
}
}
return false;
}
/*=================================================*/
signed main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n = read(); m = read(); t = read();
double l = 0.0, r = 100.0;
for(int i = 1;i <= m;i ++) {
bian[i].pos = read();
bian[i].u = read();
bian[i].v = read();
bian[i].val = read() * 1.0;// 储存边的情况
// 因为之后的连边并不是 k 而是 (k - T)
}
for(int i = 1;i <= t;i ++) {
int iid,pts;
iid = read(); pts = read();
id[iid] = pts;//已有的成绩。
}
double ans = -1;
int iii = 0;
while(r - l > EPS) {
double mid = (r + l) / 2.0;
if(check(mid)) {
l = mid + EPS;
ans = mid;
}
else r = mid - EPS;
//cout << r <<" " << l <<" "<<iii ++ << endl;
}
if(ans == -1) printf("-1");
else printf("%.10lf",ans);
return 0;
}
这玩意跑的并不快,如果你想冲入 \(50 ms\) 以内,开始我们的优化 :
1. 基本优化
-
\(SPFA\) 优化 ——— \(SLF\) 。 \(LLL\) 优化我没试。
-
剪枝 : 已知两个人成绩,且知道两者之间的女装关系符合条件一或条件二,可以直接返回,因为这时存在环路。
2 . 玄学优化
一般来说不靠谱... 考试千万别用。
-
减少一下二分区间;
-
减少一下精度;
-
判断环的时候,如果入队次数大于 \(n + 1\) 改为 \(\sqrt{n + 1}\),或者 \(\sqrt{\sqrt{n + 1}}\); 这个思想还是我从 P3084 最后一个点学来的。这玩意看 \(RP\) ,开方次数过多会导致判断出错,一般来说进了很多次队列就很有可能是环。
于是完美的从 \(270ms\) 卡到了 \(27ms\) 到了最优解 。
【Code】
/*
By : Ti_Despairy
Time : 2021,1,21;
思路 :
知识点 :
*/
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
#include<stdlib.h>
#include<time.h>
#include<map>
#include<vector>
#include<set>
#define ull unsigned long long
#define ll long long
#define M 1000010
#define N 1010
#define qaq cout<<"可行QAQ"<<endl
#define INF 0x3f3f3f3f
using namespace std;
const int mod1 = 19260817;
const int mod2 = 19660813;
const double EPS = 1e-5;//确保精度
/*================================================*/
int n,m,cnt,t;
struct node{
int next;
int to;
double dis;
}e[N << 1];
struct nod{
int pos;
int u;
int v;
double val;
}bian[N << 1];
int head[N];
double w[N];
int vis[N];
int in[N];//入队次数
int id[N];
/*================================================*/
inline int read()
{
int s = 0, f = 0;char ch = getchar();
while (!isdigit(ch)) f |= ch == '-', ch = getchar();
while (isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar();
return f ? -s : s;
}
void add(int from,int to,double dis)
{
e[++ cnt].to = to;
e[cnt].dis = dis;
e[cnt].next = head[from];
head[from] = cnt;
return;
}
bool check(double T)
{
deque <int> qp;
while(qp.size()) qp.pop_front();
for(int i = 1;i <= n;i ++) w[i] = 1;
// 有时候当你一直输出 0 时 ,看看这个 。。。
memset(head,0,sizeof(head));
memset(vis,0,sizeof(vis));
memset(in,0,sizeof(in));
cnt = 0;
// 数据清空
for(int i = 1;i <= m;i ++) {//连边
if(id[bian[i].u] && id[bian[i].v] && ((bian[i].pos == 1 && id[bian[i].u] < id[bian[i].v] * (bian[i].val - T))
|| (bian[i].pos == 2 && id[bian[i].u] < id[bian[i].v] / (bian[i].val + T )))) return true;
if(bian[i].pos == 1) add(bian[i].v ,bian[i].u ,bian[i].val - T);
// x < y * (k - T) ;
else add(bian[i].v ,bian[i].u ,1.0 / (bian[i].val + T));
// x < y * 1 / (k + T);
}
for(int i = 1;i <= t;i ++) {
// 由已知数据来建立超级源点, 所有的情况建立在这之上
if(id[i]) {
add(0,i,id[i]);
add(i,0,1.0 / id[i]);
}
}
w[0] = 1;
vis[0] = 1;
in[0] = 1;//入队次数
qp.push_back(0);
while(!qp.empty()) {
int x = qp.front();
qp.pop_front();
//puts("LKP AK IOI");
vis[x] = false;
for(int i = head[x];i; i = e[i].next) {
int y = e[i].to;
if(w[y] < w[x] * e[i].dis) {
w[y] = w[x] * e[i].dis;
if(!vis[y]) {
vis[y] = true;
if(!qp.empty() && w[y] < w[qp.front()]) qp.push_back(y);
else qp.push_front(y);
++ in[y];
if(in[y] >= sqrt(sqrt(n + 1))) return true;
}
}
}
}
return false;
}
/*=================================================*/
signed main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n = read(); m = read(); t = read();
double l = 0.0, r = 4.0;
for(int i = 1;i <= m;i ++) {
bian[i].pos = read();
bian[i].u = read();
bian[i].v = read();
bian[i].val = read() * 1.0;// 储存边的情况
// 因为之后的连边并不是 k 而是 (k - T)
}
for(int i = 1;i <= t;i ++) {
int iid,pts;
iid = read(); pts = read();
id[iid] = pts;//已有的成绩。
}
double ans = -1;
int iii = 0;
while(r - l > EPS) {
double mid = (r + l) / 2.0;
if(check(mid)) {
l = mid + EPS;
ans = mid;
}
else r = mid - EPS;
//cout << r <<" " << l <<" "<<iii ++ << endl;
}
if(ans == -1) printf("-1");
else printf("%.10lf",ans);
return 0;
}