【前言】
要开学了,比较颓废,龟速水题。
2200+,2300-居然看不了gym的数据qwq
由于比较长我觉得完全可以分两篇来写。
有不少代码是看着写的
这里是上篇。
下篇请移步这里
【题目】
gym
A.Airport Construction
给定一个
n
n
n个点的多边形,求多边形内最长线段的长度。
n
≤
200
,
∣
x
i
∣
,
∣
y
i
∣
≤
1
0
9
n\leq 200,|x_i|,|y_i|\leq 10^9
n≤200,∣xi∣,∣yi∣≤109
可以发现答案的直线一定经过两个端点。
于是枚举答案经过哪两个端点,然后和其他线段求交,往左右两边扩展即可。
复杂度
O
(
n
3
)
O(n^3)
O(n3)
#include<bits/stdc++.h>
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef double db;
const int N=205;
const db eps=1e-8;
int n,cnt;
db ans;
pair<db,int> t[N];
struct Point
{
db x,y;
Point(db _x=0,db _y=0):x(_x),y(_y){}
Point operator + (const Point&rhs)const{return Point(x+rhs.x,y+rhs.y);}
Point operator - (const Point&rhs)const{return Point(x-rhs.x,y-rhs.y);}
db operator * (const Point&rhs)const{return x*rhs.y-y*rhs.x;}
db getlen(){return sqrt(x*x+y*y);}
}a[N];
struct Line
{
Point a,b;
Line (Point _a=Point(0,0),Point _b=Point(0,0)):a(_a),b(_b){}
};
db getdis(const Line x,const Line y)
{
return ((y.a-x.a)*(y.b-x.a))/((y.b-y.a)*(x.b-x.a))*(x.b-x.a).getlen();
}
int O(db x){return fabs(x)<eps?0:(x<0?-1:1);}
void solve(Line l)
{
cnt=0;
for(int i=1;i<=n;++i)
{
Line r=Line(a[i],a[i+1]);
int p=O((l.b-l.a)*(r.a-l.a)),q=O((l.b-l.a)*(r.b-l.a));
if(p==q) continue;//no cross
t[++cnt]=mkp(getdis(l,r),(p>q?1:-1)*((p && q)?2:1));
}
sort(t+1,t+cnt+1);
int res=0;db len=0;
for(int i=1;i<=cnt;++i)
{
if(res) len+=t[i].fi-t[i-1].fi;
else ans=max(ans,len),len=0;
res+=t[i].se;
}
ans=max(ans,len);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%lf%lf",&a[i].x,&a[i].y);
a[n+1]=a[1];
for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) solve(Line(a[i],a[j]));
printf("%.10lf\n",ans);
return 0;
}
B.Get a Clue!
有 3 3 3种类型的卡牌,每种类型分别有 6 , 6 , 9 6,6,9 6,6,9张,三种类型的卡牌分别会被移除一张。
现在有四个人游戏,每个人按顺序拿到剩下的卡牌(可能数量不同),有 n n n轮轮流的决策,每轮询问人提出一个题意猜测被移除的牌分别是什么,然后按顺时针方向反驳。若一个人手中有可以反驳的牌,需要展示任意一张可反驳牌给询问人,然后结束这轮决策。
假设你是
1
1
1号玩家,现在给出每轮的决策(有些结果不可见,即询问人和反驳人都不是你),问你可以是否可以确定每种类型的卡牌移除了什么。
n
≤
50
n\leq 50
n≤50
数据范围看起来很小,于是我们枚举一下移除了什么,然后再枚举一下其他三个人的牌,判断一遍。这样子显然是过不了的。注意到每个状态对与一个玩家是否合法是独立的,因此不妨先搜出每个人合法的状态有哪些,再进行上面的爆搜。
复杂度玄学。
#include<bits/stdc++.h>
#define ppc(x) __builtin_popcount(x)
using namespace std;
const int N=51,P=4,D=1<<21,num[P]={5,5,4,4};
int n,ban;
int sz[P],lim[P][N],hv[P+1];
char ch[P];
vector<int>ans;
bool dfs(int dp,int msk,int r)
{
if(dp==P) return 1;
if(ppc(msk)==num[dp])
{
for(int i=0;i<sz[dp];++i)
if(((msk&lim[dp][i])>0)^((lim[dp][i]&D)>0)) return 0;
ban^=msk^hv[dp];
bool res=dfs(dp+1,hv[dp+1],~ban);
ban^=msk^hv[dp];
return res;
}
for(;;)
{
int t=r&(-r);
if(t>=D) break;
if(dfs(dp,msk|t,r^=t)) return 1;
}
return 0;
}
int gnex(int x){return (x+1)%P;}
void init()
{
scanf("%d",&n);
for(int i=0;i<5;++i) scanf("%s",ch),hv[0]|=1<<(ch[0]-'A');
for(int i=0;i<n;++i)
{
int msk=0;
for(int j=0;j<3;++j) scanf("%s",ch),msk|=1<<(ch[0]-'A');
for(int j=1,k=gnex(i);j<P;++j,k=gnex(k))
{
scanf("%s",ch);
if(ch[0]=='-') lim[k][sz[k]++]=msk;
else
{
if(ch[0]=='*') lim[k][sz[k]++]=msk|D;
else hv[k]|=1<<(ch[0]-'A');
break;
}
}
}
for(int i=0;i<P;++i)
{
ban|=hv[i];
sort(lim[i],lim[i]+sz[i]);
sz[i]=unique(lim[i],lim[i]+sz[i])-lim[i];
}
for(int i=0;i<6;++i) if(~ban&(1<<i))
for(int j=6;j<12;++j) if(~ban&(1<<j))
for(int k=12;k<21;++k) if(~ban&(1<<k))
{
//printf("%d %d %d\n",i,j,k);
int msk=(1<<i)+(1<<j)+(1<<k);
ban^=msk;
if(dfs(1,hv[1],~ban)) ans.push_back(msk);
ban^=msk;
}
//for(int i=0;i<(int)ans.size();++i) printf("%d\n",ans[i]);
}
void getans()
{
int i,j,tot=(int)ans.size();
for(i=0;i<6;++i)
{
for(j=0;j<tot;++j) if(~ans[j]&(1<<i)) break;
if(j==tot) {ch[0]='A'+i;break;}
}
if(i==6) ch[0]='?';
for(i=6;i<12;++i)
{
for(j=0;j<tot;++j) if(~ans[j]&(1<<i)) break;
if(j==tot) {ch[1]='A'+i;break;}
}
if(i==12) ch[1]='?';
for(i=12;i<21;++i)
{
for(j=0;j<tot;++j) if(~ans[j]&(1<<i)) break;
if(j==tot) {ch[2]='A'+i;break;}
}
if(i==21) ch[2]='?';
ch[3]='\0';puts(ch);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
init();
getans();
return 0;
}
C.Mission Improbable
一个空间中有一堆箱子排成
n
n
n行
m
m
m列,每个位置的箱子个数分别为
x
i
,
j
x_{i,j}
xi,j。
问最多能去掉多少个箱子,使得重新放置这些箱子后,三视图不变。
n
,
m
≤
100
,
x
i
,
j
≤
1
0
9
n,m\leq 100,x_{i,j}\leq 10^9
n,m≤100,xi,j≤109
观察到对于俯视图,只需要这个位置上不拿完即可。对于正视图和侧视图,要求这一行/列的最大值不变。
如果有一行一列的最大值相同,我们不妨将这两个最大值合并起来,即放在它们的交点位置。
于是我们将最大值相同的行列之间连边,这样子实际上就是一个二分图最大匹配问题,尽可能将最大值放在交点位置。然后最后统计一遍答案即可。
一个点数 O ( n ) O(n) O(n),边数 O ( n 2 ) O(n^2) O(n2)的二分图匹配,就不写网络流了吧- -。
#include<bits/stdc++.h>
using namespace std;
const int N=205;
int n,m,tot;
int head[N],mr[N],ml[N],a[N][N],vis[N],used[N];
long long ans;
struct Tway{int v,nex;}e[N*N];
void add(int u,int v){e[++tot]=(Tway){v,head[u]};head[u]=tot;}
bool dfs(int x)
{
for(int i=head[x];i;i=e[i].nex)
{
int v=e[i].v;
if(vis[v]) continue;
vis[v]=1;if(!used[v] || dfs(used[v])) {used[v]=x;return 1;}
}
return 0;
}
void solve()
{
for(int i=1;i<=n;++i) if(mr[i]) ans-=mr[i]-1;
for(int i=1;i<=m;++i) if(ml[i]) ans-=ml[i]-1;
for(int i=1;i<=n;++i)
{
memset(vis,0,sizeof(vis));
if(mr[i] && dfs(i)) ans+=mr[i]-1;
}
printf("%lld\n",ans);
}
void init()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j)
{
scanf("%d",&a[i][j]);
mr[i]=max(mr[i],a[i][j]);ml[j]=max(ml[j],a[i][j]);
if(a[i][j]) ans+=a[i][j]-1;
}
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j)
if(mr[i]==ml[j] && mr[i] && a[i][j]) add(i,n+j);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
init();solve();
return 0;
}
D.Money for Nothing
有
n
n
n个生产公司和
m
m
m个消费公司。每个生产公司有开始生产时间和零件价格
(
s
i
,
a
i
)
(s_i,a_i)
(si,ai),你可以在生产时间开始每天在生产公司买一个零件。每个消费公司有截止消费时间和接收零件价格
(
t
i
,
b
i
)
(t_i,b_i)
(ti,bi),你可以在截至消费时间之前每天卖给消费公司零件。问在选择一家生产公司和一家消费公司的情况下最大利润。
n
,
m
≤
5
×
1
0
5
n,m\leq 5\times 10^5
n,m≤5×105,其他数字不超过
1
0
9
10^9
109
实际上要求的就是 max { ( t i − s i ) ∗ ( b i − a i ) } \max\{ (t_i-s_i)*(b_i-a_i)\} max{(ti−si)∗(bi−ai)}。
这个东西显然是有单调性的,即对于 t i t_i ti越大的消费公司选择 s i s_i si一定也是单调增的。
考虑问题的本质实际上就是求矩形面积最大emmm,证明就随便取四个点比较一下大小就行了。
分治可以做到 O ( n log n ) O(n\log n) O(nlogn)
#include<bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N=5e5+10,inf=0x3f3f3f3f;
const ll llinf=1ll*inf*inf;
int n,m,cnt;
pii a[N],b[N];
ll ans,f[N];
void solve(int l,int r,int L,int R)
{
if(l>r) return;
int mid=(l+r)>>1,pos;
//cerr<<l<<" "<<r<<" "<<L<<" "<<R<<endl;
for(int i=L;i<=R;++i)
{
if(b[i].fi<a[mid].fi && b[i].se<a[mid].se) continue;
ll t=1ll*(b[i].fi-a[mid].fi)*(b[i].se-a[mid].se);
if(f[mid]<t) f[mid]=t,pos=i;
}
ans=max(ans,f[mid]);
solve(l,mid-1,L,pos);solve(mid+1,r,pos,R);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%d%d",&a[i].fi,&a[i].se);
for(int i=1;i<=m;++i) scanf("%d%d",&b[i].fi,&b[i].se);
sort(a+1,a+n+1);
for(int i=1,now=inf;i<=n;)
{
if(a[i].se<now) f[cnt]=-llinf,a[++cnt]=a[i],now=a[i].se;
for(int j=a[i].fi;i<=n && a[i].fi==j;++i);
}
n=cnt;cnt=0;
sort(b+1,b+m+1,greater<pii>());
for(int i=1,now=0;i<=m;)
{
if(b[i].se>now) b[++cnt]=b[i],now=b[i].se;
for(int j=b[i].fi;i<=m && b[i].fi==j;++i);
}
m=cnt;reverse(b+1,b+m+1);
solve(1,n,1,m);
printf("%lld\n",ans);
return 0;
}
E.Need for Speed
有
n
n
n段行程,共花费了
t
t
t的时间。已知每段行程的距离
d
i
d_i
di和表盘读数是
s
i
+
c
s_i+c
si+c,求常数
c
c
c。
∣
d
i
∣
,
∣
s
i
∣
,
n
≤
1000
,
t
≤
1
0
6
|d_i|,|s_i|,n\leq 1000,t\leq 10^6
∣di∣,∣si∣,n≤1000,t≤106
显然可以二分答案,答案的上界应该是
2
×
1
0
6
2\times 10^6
2×106,下界是
−
1
0
3
-10^3
−103。复杂度
O
(
n
log
a
n
s
)
O(n\log ans)
O(nlogans)
(按道理来说上界是
1
0
6
10^6
106吧,然而过不了。
#include<bits/stdc++.h>
using namespace std;
typedef double db;
const int N=1005;
const db eps=1e-10;
int n;
db T,d[N],s[N];
bool check(db c)
{
db tot=0;
for(int i=1;i<=n;++i)
{
if(s[i]+c<eps) return 1;
tot+=d[i]/(s[i]+c);
}
//cerr<<c<<" "<<tot<<endl;
return tot>T;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
scanf("%d%lf",&n,&T);
for(int i=1;i<=n;++i) scanf("%lf%lf",&d[i],&s[i]);
db l=-1000,r=2e6;
while(l+eps<r)
{
db mid=(l+r)/2;
if(check(mid)) l=mid+eps;
else r=mid-eps;
}
printf("%.10lf\n",l-eps);
return 0;
}
F.Posterize
给定
n
n
n种匹配点,强度为
r
i
r_i
ri,有
p
i
p_i
pi个。从中选择
k
k
k种强度,定义平方误差为
∑
i
=
1
n
r
i
∗
min
1
≤
j
≤
k
(
r
i
−
r
j
)
2
\sum_{i=1^n} r_i*\min\limits_{1\leq j\leq k} (r_i-r_j)^2
∑i=1nri∗1≤j≤kmin(ri−rj)2。最小化平方误差。
所有数字不超过
256
256
256
设 f i , j f_{i,j} fi,j表示前 i i i种强度选择了 j j j种,且第 i i i种一定选择时的最小平方误差和。那么转移时枚举上一种选了什么,不难得到第 j − 1 j-1 j−1种和第 j j j种的贡献区间,记录前缀和优化转移即可。
实现时可以直接按 n = 256 n=256 n=256来做,还可以使用区间 DP \text{DP} DP的方式来计算。
复杂度 O ( n 3 ) O(n^3) O(n3)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=256,M=N+10;
int n,K;
ll ans,sum,cnt[M],sq[M],f[M][M];//wrong because use N
ll calc(int l,int r,int p){return (cnt[r]-cnt[l])*p*p-(sq[r]-sq[l])*p;}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
scanf("%d%d",&n,&K);
for(int i=1,x;i<=n;++i) scanf("%d",&x),scanf("%lld",&cnt[x]),sq[x]=cnt[x]*x*2,sum+=cnt[x]*x*x;
for(int i=1;i<N;++i) cnt[i]+=cnt[i-1],sq[i]+=sq[i-1];
memset(f,0x3f,sizeof(f));ans=f[0][0];
for(int i=1;i<=K;++i)
{
if(i==1) for(int j=0;j<N;++j) f[1][j]=cnt[j]*j*j-sq[j]*j;
else
{
for(int j=i-1;j<N;++j) for(int k=i-2;k<j;++k)
{
int mid=(j+k)>>1;
f[i][j]=min(f[i][j],f[i-1][k]+calc(k,mid,k)+calc(mid,j,j));
}
}
if(i==K) for(int j=i-1;j<N;++j) f[i][j]+=calc(j,N-1,j),ans=min(ans,f[i][j]);
}
printf("%lld\n",ans+sum);
return 0;
}