1003 Forgiving Matching
题目
问题描述
Alice和Bob正在玩游戏。在这个游戏中,二维平面上有 n 条直线。 Alice 将首先在所有 n 条直线中选择恰好 k 条直线 l1,l2,…,lk,然后 Bob 将画一条直线 L。 Bob 的惩罚定义为 {l1,l2,…, lk} 与 L 共享至少一个公共点。请注意,两条重叠的线也共享公共点。
Alice 想要最大化 Bob 的惩罚,而 Bob 想要最小化它。你将得到这 n 行,请编写一个程序来预测 Bob 对 k=1,2,3,…,n 的惩罚,如果两个玩家都玩得最好。
输入
第一行包含一个整数 T (1≤T≤500),即测试用例的数量。对于每个测试用例:
第一行包含一个整数 n (1≤n≤100000),表示直线的数量。
接下来的 n 行中的每一行都包含四个整数 xai,yai,xbi 和 ybi (0≤xai,yai,xbi,ybi≤109),表示一条直线通过 (xai,yai) 和 (xbi,ybi)。 (xai,yai) 永远不会与 (xbi,ybi) 重合。
保证所有 n 的总和最多为 1000000。
输出
对于每个测试用例,输出n行,其中第i行(1≤i≤n)包含一个整数,表示当k=i时Bob的惩罚。
样本输入
2
2
1 1 2 2
0 0 2 3
3
1 1 2 2
1 1 2 2
3 2 5 4
样本输出
0
1
0
0
0
解题思路
两条直线不存在公共点当且仅当它们平行即斜率相同。
已知现在有k条直线,Bob为了使有交点的直线尽可能少,就要使与直线L平行的直线尽可能多,因此其最小惩罚就是k-n_max
Alice 为了让Bob 与尽量多的直线相交,就是要使斜率出现次数的最大值最小,即最大化惩罚就要最小化n_min
因此,首先计算出所有直线的斜率,然后从小到大排序,然后不断从前向后循环取斜率,每轮每种斜率只取一个,这样能够保证最小化数量最多的斜率的数量。
代码
#include<bits/stdc++.h>
const int maxn=1e5+5;
int t,n;
map<pair<int,int>,int>M;
int gcd(int x,int y)
{
return y?gcd(y,x%y):x
}
int abs(int s)
{
return x<0?-x:x;
}
struct node
{
int no,num;
bool operator<(const node &other) const { return num > other.num}
}num[maxn];
int main()
{
for(scanf("%d",&t);t--;)
{
int top=0;
M.clear();
scanf("%d",&n);
for(int i=1,xi,x2,y1,y2;i<=n;i++)
{
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
int y=y2-y1,x=x2-x1;
int f=1ll*y*x<0?-1:1;
int g=gcd(abs(x),abs(y));
y=abs(y)/g,x=abs(x)/g;
if(!M.count({f*y,x})) M[{f*y,x}]=++top,num[top]={top,0};
num[M[{f*y,x}]].num++;
}
sort(num+1,num+top+1);
int now=1;
}
}
1009 Rise in Price
题目
问题描述
网格上有 n×n 个单元格,左上角的单元格在 (1,1) 处,而右下角的单元格在 (n,n) 处。你从 (1,1) 开始,然后移动到 (n,n)。在任何单元格 (i,j) 处,您可以移动到 (i+1,j) 或 (i,j+1),前提是您不移出网格。显然,您将精确地进行 2n−2 步。
当你在格子(i,j),包括起点(1,1)和目的地(n,n),你可以拿走这个格子里所有的ai,j钻石,有机会提价每颗钻石按 bi,j 美元。您将最终以最终价格出售您拥有的所有钻石,您的目标是选择将使您的利润最大化的最佳路径。请注意,最初每颗钻石的价格为零,您没有什么可出售的。
输入
第一行包含一个整数 T (1≤T≤10),即测试用例的数量。对于每个测试用例:
第一行包含一个整数 n (1≤n≤100),表示网格的大小。
以下n行每行包含n个整数,第i行包含ai,1,ai,2,…,ai,n (1≤ai,j≤106),表示每个单元格中的菱形数量。
以下n行每行包含n个整数,第i行包含bi,1,bi,2,…,bi,n (1≤bi,j≤106),表示每个单元格可以提价多少.
保证 ai,j 和 bi,j 的所有值都是从 [1,106] 中的整数中均匀随机选择的。随机性条件不适用于样本测试用例,但您的解决方案也必须通过样本。
输出
对于每个测试用例,输出一行包含一个整数:您可以通过出售钻石赚取的最大美元数。
样本输入
1
4
2 3 1 5
6 3 2 4
3 5 1 4
5 2 4 1
3 2 5 1
2 4 3 5
1 2 3 4
4 3 5 3
样本输出
528
解题思路
设 fi,j,k表示从*(1, 1)*走到 (i, j),一路上收集了 k 个钻石时,钻石的单价最高能涨到多少,
则 ans= max(k × fn,n,k)。
对于固定的 (i, j) 来说,考虑两个状态 fi,j,x 和 f**i,j,y,其中 x < y,如果 fi,j,x ≤ fi,j,y,则
状态 fi,j,x 一定不可能发展为最优解,可以剔除。对于每个 (i, j),用列表按照 k 升序保存所有
状态,并剔除不可能成为最优解的状态即可。
随机数据下当 n = 100 时,单个 (i, j) 的有效状态的峰值 k 大约为几千。时间复杂度
O(n2k)。
代码
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
typedef pair<int,int>P;
typedef vector<P>V;
const int N=105;
int Case,n,m,i,j,k,a[N][N],b[N][N];ll ans;V f[N][N];P pool[1000005];
inline void ext(const P&t){
while(m&&pool[m].second<=t.second)m--;
if(!m||pool[m].first<t.first)pool[++m]=t;
}
inline void merge(const V&A,const V&B,V&C){
int ca=A.size(),cb=B.size(),i=0,j=0;
m=0;
while(i<ca&&j<cb)ext(A[i].first<B[j].first?A[i++]:B[j++]);
while(i<ca)ext(A[i++]);
while(j<cb)ext(B[j++]);
C.resize(m);
for(i=0;i<m;i++)C[i]=pool[i+1];
}
int main(){
scanf("%d",&Case);
while(Case--){
scanf("%d",&n);
for(i=1;i<=n;i++)for(j=1;j<=n;j++)scanf("%d",&a[i][j]);
for(i=1;i<=n;i++)for(j=1;j<=n;j++)scanf("%d",&b[i][j]);
f[1][1].resize(1);
f[1][1][0]=P(a[1][1],b[1][1]);
for(i=1;i<=n;i++)for(j=1;j<=n;j++){
if(i==1&&j==1)continue;
if(i==1)f[i][j]=f[i][j-1];
else if(j==1)f[i][j]=f[i-1][j];
else merge(f[i-1][j],f[i][j-1],f[i][j]);
for(k=0;k<f[i][j].size();k++){
f[i][j][k].first+=a[i][j];
f[i][j][k].second+=b[i][j];
}
}
ans=0;
for(i=0;i<f[n][n].size();i++)ans=max(ans,1LL*f[n][n][i].first*f[n][n][i].second);
printf("%lld\n",ans);
}
}
1010 Road Discount
题目
问题描述
Byteland有n个城市,用1到n标记。 Byteland 交通建设局正计划在这些城市之间建设 n-1 条双向道路,使得每对不同的城市都通过这些道路直接或间接连接。
工程公司提供了 m 条可能的候选道路进行施工。第i个候选道路将花费ci美元,如果最终建成,将有一条道路直接连接第i个城市和第vi个城市。幸运的是,每条道路都有其折扣价,其中第 i 个为 di。
Byteland 交通建设局最多可以以折扣价购买 k 条道路。请编写一个程序来帮助交通建设局找到 k=0,1,2,…,n−1 的最便宜的解决方案。
输入
第一行包含一个整数 T (1≤T≤10),即测试用例的数量。对于每个测试用例:
第一行包含两个整数n和m(2≤n≤1000,n−1≤m≤200000),分别表示城市数和候选道路数。
以下m行每行包含四个整数ui、vi、ci和di(1≤ui,vi≤n,ui≠vi,1≤di≤ci≤1000),描述一条候选道路。
输出
对于每个测试用例,输出 n 行,其中第 i 行 (1≤i≤n) 包含一个整数,表示当 k=i-1 时构建 n-1 条道路的最便宜的总成本。
保证答案永远存在。
样本输入
1
5 6
1 2 1 1
2 3 2 1
2 4 3 2
2 5 4 3
1 3 5 3
4 5 6 1
样本输出
10
7
6
5
5
解题思路
将原始边作为白边,折扣边作为黑边,由于同一条边不可能选择两次,那么问题等价于求
包含恰好 k 条黑边的最小生成树。这是一个经典问题,令 f(k) 表示包含恰好 k 条黑边的最小
生成树的边权和,则 f(k) 是一个凸函数,求出 f(k) 的方法为:
• 选择参数 c,将每条黑边的边权都加上 c。
• 求出修改边权后的图的最小生成树,令 sum(c) 为对应的边权和,l(c) 为最小生成树中使
用黑边数量的最小值,r(c) 为最小生成树中使用黑边数量的最大值。
• 二分找到合适的参数 c,满足 l(c) ≤ k ≤ r(c),则 f(k) = sum(c) k × c。
由于边权在 [1*,* 1000] 之间,因此可以预处理出 c = 1000 . . . 1000 的所有信息,一共需要
求 O(c) 次最小生成树。注意到如果对黑边或者白边单独求最小生成树,则非树边不可能用到,
因此可以将边数缩减至 O(n)。
总时间复杂度 O(m log n + nc log n)。
代码
#include<cstdio>
#include<algorithm>
using namespace std;
typedef pair<int,int>P;
const int N=1005,M=200005,V=1000;
int Case,n,m,i,f[N];P fl[V*2+5];
struct E{int x,y,w;}a[M],b[M];
inline bool cmp(const E&a,const E&b){return a.w<b.w;}
int F(int x){return f[x]==x?x:f[x]=F(f[x]);}
inline bool merge(int x,int y){
if(F(x)==F(y))return 0;
f[f[x]]=f[y];
return 1;
}
inline void reduce(E*e){
sort(e+1,e+m+1,cmp);
for(int i=1;i<=n;i++)f[i]=i;
for(int i=1,cnt=0;i<=m;i++)if(merge(e[i].x,e[i].y))e[++cnt]=e[i];
}
inline P call(int k){
for(int i=1;i<=n;i++)f[i]=i;
int A=1,B=1,sum=0,cnt=0;
while(A<n&&B<n){
if(a[A].w<=b[B].w+k){
if(merge(a[A].x,a[A].y))sum+=a[A].w;
A++;
}else{
if(merge(b[B].x,b[B].y))sum+=b[B].w+k,cnt++;
B++;
}
}
while(A<n){
if(merge(a[A].x,a[A].y))sum+=a[A].w;
A++;
}
while(B<n){
if(merge(b[B].x,b[B].y))sum+=b[B].w+k,cnt++;
B++;
}
return P(sum,cnt);
}
inline int ask(int k){
for(int i=-V;i<=V;i++)if(fl[i+V].second<=k)return fl[i+V].first-k*i;
return -1;
}
int main(){
scanf("%d",&Case);
while(Case--){
scanf("%d%d",&n,&m);
for(i=1;i<=m;i++){
scanf("%d%d%d%d",&a[i].x,&a[i].y,&a[i].w,&b[i].w);
b[i].x=a[i].x,b[i].y=a[i].y;
}
reduce(a);
reduce(b);
for(i=-V;i<=V;i++)fl[i+V]=call(i);
for(i=0;i<n;i++)printf("%d\n",ask(i));
}
}