P4636 [SHOI2011] 直线拟合
题意
一个工厂,每个时刻可以选择增加 111 生产力或生产出当前生产力大小的产品
先给出 nnn 个订单,可以选择一些完成
第 iii 个订单要求在 tit_iti 时刻交付 gig_igi 个产品,同时会给出 mim_imi 的报酬
问最大能获得多少报酬
其中,n≤15,ti≤105,gi≤109,mi≤109n\leq15,t_i\leq10^5,g_i\leq10^9,m_i\leq10^9n≤15,ti≤105,gi≤109,mi≤109
题解
考虑先 2n2^n2n 枚举所有的完成订单情况,于是问题由最优求解转化为判断
先假设我们考虑到第 iii 个订单,枚举后面的订单 jjj
令 T=tj−ti,Need=∑k=i+1jgk,XT=t_j-t_i,Need=\sum_{k=i+1}^jg_k,XT=tj−ti,Need=∑k=i+1jgk,X 为 [ti+1,tj]\big[t_i+1,t_j\big][ti+1,tj] 中加了几次生产力,MMM 为当前的生产力,RRR 为当前暂时存着没有提供的产品数量
则显然有 (M+X)(T−X)+R>=Need(M+X)(T-X)+R>=Need(M+X)(T−X)+R>=Need
展开是一个二次函数的形式,容易求解出 XXX 的范围
但,这么多个 XXX 的范围内,只有 iii 和 i+1i+1i+1 的这个解集是 '实' 的,因为其余的并不能保证是全部加生产力在全部生产
于是我们取 XXX 为 iii 和 i+1i+1i+1 的解集中最大的一个,顺序模拟即可
时间复杂度 O(n2×2n)O(n^2\times2^n)O(n2×2n)
代码
cpp
#include<bits/stdc++.h>
# define ll long long
# define Maxn 20
# define db double
using namespace std;
int n,s[Maxn],top;
ll sum,ans;
struct Node{
ll t,g,m;
bool operator < (const Node &x) const{return t<x.t;};
}a[Maxn];
ll Calc(ll A,ll B,ll C) {
if(B*B-4ll*A*C<0) return -1;
db X=((-B+sqrt(B*B-4ll*A*C))*1.0)/(2ll*A);
return floor(X);
}
bool check() {
ll M=1,R=0;
for(int i=1;i<top;i++) {
ll Mn=a[s[i+1]].t-a[s[i]].t,Need=0;
for(int j=i+1;j<=top;j++) {
Need+=a[s[j]].g;
ll X=Calc(1,M-(a[s[j]].t-a[s[i]].t),Need-M*(a[s[j]].t-a[s[i]].t)-R);
Mn=min(Mn,X);
}
// if(top==3) {printf("%d: %lld\n",i,Mn);}
if(Mn<0) return 0;
R=(M+Mn)*(a[s[i+1]].t-a[s[i]].t-Mn)+R-a[s[i+1]].g;
M+=Mn;
}
return 1;
}
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld%lld%lld",&a[i].t,&a[i].g,&a[i].m);
sort(a+1,a+n+1);
for(int S=0;S<(1<<n);S++) {
s[++top]=0;
for(int i=0;i<n;i++)
if((S>>i)&1) s[++top]=i+1,sum+=a[i+1].m;
if(check()) ans=max(ans,sum);
top=sum=0;
}
printf("%lld\n",ans);
return 0;
}
P3828 [SHOI2012] 火柴游戏
题意
P3828 [SHOI2012] 火柴游戏
题目背景
SHOI2012D1T1
题目描述
小明非常喜欢玩火柴游戏:首先用火柴棒摆出一个可能是错误的等式,然后通过添加、删除或移动火柴棒,使得等式成立。下图展示每个数字的样子:

我们只考虑形如"A = B"的式子,其中 A 和 B 是两个具有相同位数的整数。
小明可进行三种操作:
-
在任意位置添加一根火柴棒;
-
从任意位置删除一根火柴棒;
-
将任意一根火柴棒移动到另一个位置。
在完成所有操作后,等号两侧必须都是合法的数字,且完全相等。我们约定:
-
小明不能消除任何数字,也就是说,可以删除一个数字的部分火柴,但不能令它消失;
-
小明不能增加任何数字,也就是说,可以在一个已有的数字上添加火柴,或将火柴移动到一个已有的数字上,但不能凭空增加一个数字;
-
小明不能拆分或者合并数字,比如将一个 8 变成两个 1,或者将两个 1合并成一个 8;
-
其中代表 1 的火柴棒必须靠右边摆放,放在左边不是有效的数字。每种操作都有一定的代价:
对一个添加操作,如果这是第iii次进行添加操作,这一步的费用为 p1×i+q1p_1\times i+q_1p1×i+q1
对一个删除操作,如果这是第iii次进行删除操作,这一步的费用为p2×i+q2p_2\times i+q_2p2×i+q2
对一个移动操作,如果这是第iii次进行移动操作,这一步的费用为p3×i+q3p_3\times i+q_3p3×i+q3
例如,小明在游戏中添加了 3 根火柴,移动了 1 根火柴,删除了 2 根火柴,那么他总的花费为[(p1×1+q1)+(p1×2+q1)+(p1×3+q1)]+(p3×1+q3)+[(p2×1+q2)+(p2×2+q2)][(p_1\times 1+q_1)+(p_1\times 2+q_1)+(p_1\times 3+q_1)]+(p_3\times 1+q_3)+[(p_2\times 1+q_2 )+(p_2\times 2+q_2)][(p1×1+q1)+(p1×2+q1)+(p1×3+q1)]+(p3×1+q3)+[(p2×1+q2)+(p2×2+q2)]。
小明想知道,他如何才能用最少的花费使等式成立。你能写个程序帮助他吗?
对于 100%数据,有L≤200L\le 200L≤200。
赛时思路 以及 题解
假如知道目标状态,那么就可以算出要多加几笔和删去几笔
分别记为 cnt1,cnt2cnt1,cnt2cnt1,cnt2
但由于有移动操作,所以还要枚举 cntcntcnt 为移动几次
那么如果记 gi,jg_{i,j}gi,j 为加 iii 笔,删 jjj 笔的最小代价,则有
gi,j=mink=0min(i,j)Cost1(i−k)+Cost2(j−k)+Cost3(k)g_{i,j}=min_{k=0}^{min(i,j)} Cost_1(i-k)+Cost_2(j-k)+Cost_3(k)gi,j=mink=0min(i,j)Cost1(i−k)+Cost2(j−k)+Cost3(k)
其中 Cost1/2/3iCost_{1/2/3} iCost1/2/3i 表示 增/删/移动增/删/移动增/删/移动 iii 笔的操作代价
但现在并不知道目标状态
考虑用一个 DPDPDP
设 fi,j,kf_{i,j,k}fi,j,k 为考虑前 iii 位,增 jjj 笔,删 kkk 笔能否实现
于是直接 DPDPDP 即可,时间复杂度 O(L3)O(L^3)O(L3)
代码
cpp
#include<bits/stdc++.h>
# define Maxn 205
# define ll long long
using namespace std;
bool st[15][15]={{0,1,1,1,0,1,1,1},{0,0,0,0,0,0,1,1},{0,1,0,1,1,1,1,0},{0,1,0,0,1,1,1,1},{0,0,1,0,1,0,1,1},{0,1,1,0,1,1,0,1},{0,1,1,1,1,1,0,1},{0,1,0,0,0,0,1,1},{0,1,1,1,1,1,1,1},{0,1,1,0,1,1,1,1}};
int n;
ll p1,q1,p2,q2,p3,q3,g[Maxn*10][Maxn*10],ans=1e18;
char s[Maxn],t[Maxn];
int cnt1[15][15];
int cnt2[15][15];
bitset<Maxn*10> f[Maxn][Maxn*10];
void Init() {
for(int i=0;i<=9;i++) {
for(int j=0;j<=9;j++) {
for(int k=1;k<=7;k++) {
if(st[i][k]&&(!st[j][k])) cnt2[i][j]++;
if((!st[i][k])&&st[j][k]) cnt1[i][j]++;
}
}
}
}
int main() {
scanf("%d%s%s",&n,s+1,t+1);
scanf("%lld%lld%lld%lld%lld%lld",&p1,&q1,&p2,&q2,&p3,&q3),Init();
f[0][0].set(0);
for(int i=1;i<=n;i++) {
for(int j=0;j<=min(505,i*7);j++) {
int cnts1,cnts2,cntt1,cntt2;
for(int k=0;k<=9;k++) {
cnts1=cnt1[s[i]-'0'][k],cnts2=cnt2[s[i]-'0'][k];
cntt1=cnt1[t[i]-'0'][k],cntt2=cnt2[t[i]-'0'][k];
if(j>=cnts1+cntt1)
f[i][j]|=(f[i-1][j-cnts1-cntt1]<<(cnts2+cntt2));
}
}
}
for(int i=0;i<=min(505,n*7);i++) {
for(int j=0;j<=n*7;j++) {
g[i][j]=1e18;
for(int k=0;k<=min(i,j);k++)
g[i][j]=min(g[i][j],(1ll*(i-k)*q1+1ll*(i-k)*(i-k+1)/2*p1)+
1ll*k*q3+1ll*k*(k+1)/2*p3+
1ll*(j-k)*q2+1ll*(j-k)*(j-k+1)/2*p2);
}
}
for(int i=0;i<=min(505,n*7);i++) {
for(int j=0;j<=n*7;j++)
if(f[n][i][j]) ans=min(ans,g[i][j]);
}
printf("%lld\n",ans);
return 0;
}
P4636 [SHOI2011] 直线拟合
题意
给定 nnn 个点,定义一条直线 lll 的权 D(l)=maxi=1ndis(l,i)D(l)=max_{i=1}^ndis(l,i)D(l)=maxi=1ndis(l,i)
其中 dis(l,i)dis(l,i)dis(l,i) 为第 iii 个点到直线 lll 的距离
问权最小的直线的权为多少
其中,n≤105n\leq10^5n≤105
赛时思路
考虑将所有点旋转 θ\thetaθ ,此时只用考虑竖坐标最大的两个点,ansansans 为这两个点的竖坐标的平均值
推导得,竖坐标为 x2+y2×(sinαcosθ+cosαsinθ)\sqrt{x^2+y^2}\times(sin\alpha cos\theta+cos\alpha sin\theta)x2+y2 ×(sinαcosθ+cosαsinθ)
将 sinα,cosα\sin\alpha,cos\alphasinα,cosα 替换成 x,yx,yx,y得
(x2+y2)×(cosθy+sinθx)(x^2+y^2)\times(\frac{cos\theta}{y}+\frac{sin\theta}{x})(x2+y2)×(ycosθ+xsinθ)
题解
考虑到这道题相当于用两条平行直线把整个点集包含在两条直线中间,并使直线间距离最小
而这就是旋转卡壳
代码
cpp
#include<bits/stdc++.h>
# define Maxn 100005
# define db double
# define ll long long
# define pr pair<int,int>
# define fir first
# define sec second
using namespace std;
int T,n,x,y;
int s[Maxn],top;
int S[Maxn],Top;
db ans=1e18;
struct Node{
int x,y;
bool operator < (const Node &c) const{
if(x==c.x) return y<c.y;
return x<c.x;
};
bool operator == (const Node &c) const{return (x==c.x)&&(y==c.y);};
}a[Maxn];
ll Cross(pr x,pr y) {return 1ll*x.fir*y.sec-1ll*x.sec*y.fir;}
ll Calc(int x,int y,int z) {return Cross({a[y].x-a[x].x,a[y].y-a[x].y},{a[z].x-a[y].x,a[z].y-a[y].y});}
ll qdis1(int x,int y,int z) {return abs(Cross({a[y].x-a[x].x,a[y].y-a[x].y},{a[z].x-a[x].x,a[z].y-a[x].y}));}
ll qdis2(int x,int y) {return 1ll*(a[y].x-a[x].x)*(a[y].x-a[x].x)+1ll*(a[y].y-a[x].y)*(a[y].y-a[x].y);}
int main() {T=1;
while(T--) {
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d%d",&a[i].x,&a[i].y);
sort(a+1,a+n+1),n=unique(a+1,a+n+1)-a-1;
for(int i=1;i<=n;i++) {
while(top>=2&&Calc(s[top-1],s[top],i)>=0) top--;
s[++top]=i;
}
for(int i=1;i<=top;i++) S[Top++]=s[i];
top=0;
for(int i=n;i>=1;i--) {
while(top>=2&&Calc(i,s[top],s[top-1])<=0) top--;
s[++top]=i;
}
for(int i=2;i<top;i++) S[Top++]=s[i];
int p=1;
for(int i=0;i<Top;i++) {
int j=(i+1)%Top;
while(qdis1(S[i],S[j],S[p])<qdis1(S[i],S[j],S[(p+1)%Top])) p=(p+1)%Top;
ans=min(ans,(qdis1(S[i],S[j],S[p])*1.0)/sqrt(qdis2(S[i],S[j])));
}
// printf("S ");for(int i=0;i<Top;i++) printf("%d ",S[i]);printf("\n");
printf("%.2lf\n",(ans*1.0)/2);
top=Top=0,ans=1e18;
}
return 0;
}