poj1328【Radar Installation】
题意就是求最少要用多少个半径为 (r) 的圆覆盖所有点,无解输出-1。
这种题的套路就是卡边界,也就是贪心。
首先,把点按 (x) 坐标排序。
对于第一个点,我们可以找到一个最靠右的圆,使得这个点刚好在圆上,设圆心为((a,0))。
然后对于新加入的一个点,我们判断点是否被圆 (a) 包含,若不是,对于新点再求一个 (a'),如果 (a'<a) 的话,更新 (a),否则 ans++。
因为按 (x) 排序后,上一个对答案造成贡献的点的 (x) 坐标(<=a'<=)新点的 (x) 坐标
#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
#define db double
#define ll long long
#define RG register
inline int gi()
{
RG int ret; RG bool flag; RG char ch;
ret=0, flag=true, ch=getchar();
while (ch < '0' || ch > '9')
ch == '-' ? flag=false : 0, ch=getchar();
while (ch >= '0' && ch <= '9')
ret=(ret<<3)+(ret<<1)+ch-'0', ch=getchar();
return flag ? ret : -ret;
}
const db pi = acos(-1.0);
const int N = 1005, inf = 1<<30;
struct poi
{
int x,y;
inline bool operator <(const poi &P) const { return x < P.x; }
}p[N];
int main()
{
freopen("Radar_Installation.in","r",stdin);
freopen("Radar_Installation.out","w",stdout);
int n,i,id,sum,r;
db x,tmp;
bool fg;
id=1;
while (scanf("%d",&n))
{
fg=false;
if (!n)
break;
r=gi();
for (i=1; i<=n; ++i)
p[i].x=gi(), p[i].y=gi();
for (i=1; i<=n; ++i)
if (p[i].y > r)
{
printf("Case %d: %d
",id++,-1);
fg=true;
break;
}
if (fg)
continue;
sum=0, r*=r;
sort(p+1,p+n+1);
sum++;
x=p[1].x+sqrt(r-p[1].y*p[1].y);
for (i=2; i<=n; ++i)
{
if ((p[i].x-x)*(p[i].x-x)+p[i].y*p[i].y <= r)
continue;
tmp=p[i].x+sqrt(r-p[i].y*p[i].y);
if (tmp > x)
sum++;
x=tmp;
}
printf("Case %d: %d
",id++,sum);
}
return 0;
}
poj3253【Fence Repair】
题目要求把一块木板锯成 (n) 段,每次花费的代价是锯之前的长度。求最小代价。
实际上这道题如果想到了倒过来做的话,就很水了。(但是并没有想到)
把一块木板锯成 (n) 段可以看成 (n) 段木板合成一段,每次代价是合并的两段长度之和。
于是就变成了合并果子。
碰到这种阶段性很强的题,可以考虑倒过来做。这个与一些题目中把删点变成加点来做是一样的套路。
#include <bits/stdc++.h>
using namespace std;
#define db double
#define ll long long
#define RG register
inline int gi()
{
RG int ret; RG bool flag; RG char ch;
ret=0, flag=true, ch=getchar();
while (ch < '0' || ch > '9')
ch == '-' ? flag=false : 0, ch=getchar();
while (ch >= '0' && ch <= '9')
ret=(ret<<3)+(ret<<1)+ch-'0', ch=getchar();
return flag ? ret : -ret;
}
const db pi = acos(-1.0);
const int N = 2e4+5, inf = 1<<30;
priority_queue <int> q;
int main()
{
freopen("Fence_Repair.in","r",stdin);
freopen("Fence_Repair.out","w",stdout);
int n,i,x;
ll ans;
n=gi();
for (i=1; i<=n; ++i)
x=gi(), q.push(-x);
ans=0;
while (true)
{
x=q.top(), q.pop();
x+=q.top(), q.pop(), ans-=x;
if (q.empty())
break;
q.push(x);
}
printf("%lld
",ans);
return 0;
}
poj3662 【Telephone Lines】
题意是求 (1) 到 (n) 的第 (k+1) 大的边最小。
和最大边最小一样,二分答案,然后跑 (spfa),算出 (1~n) 最少要经过多少条大于 (mid) 的边。
#include <bits/stdc++.h>
using namespace std;
#define db double
#define ll long long
#define RG register
inline int gi()
{
RG int ret; RG bool flag; RG char ch;
ret=0, flag=true, ch=getchar();
while (ch < '0' || ch > '9')
ch == '-' ? flag=false : 0, ch=getchar();
while (ch >= '0' && ch <= '9')
ret=(ret<<3)+(ret<<1)+ch-'0', ch=getchar();
return flag ? ret : -ret;
}
const db pi = acos(-1.0);
const int N = 1005, M = 2e4+5, inf = 1<<30;
int n,m,k,mid,num,f[N],to[M],len[M],nx[M],dis[N],E[M];
bool vis[N];
queue <int> q;
inline void add(int x,int y,int z)
{
to[++num]=y, len[num]=z, nx[num]=f[x], f[x]=num;
}
inline bool spfa()
{
int i,o,et,ed;
for (i=2; i<=n; ++i)
dis[i]=inf, vis[i]=false;
vis[1]=true, q.push(1);
while (!q.empty())
{
o=q.front(), q.pop(), vis[o]=false;
for (i=f[o]; i; i=nx[i])
{
et=to[i], ed=len[i];
if (dis[et] > dis[o]+(ed > E[mid]))
{
dis[et]=dis[o]+(ed > E[mid]);
if (!vis[et])
q.push(et), vis[et]=true;
}
}
}
return dis[n] <= k;
}
int main()
{
freopen("Telephone_Lines.in","r",stdin);
freopen("Telephone_Lines.out","w",stdout);
n=gi(), m=gi(), k=gi();
int i,x,y,z;
for (i=1; i<=m; ++i)
x=gi(), y=gi(), z=gi(), add(x,y,z), add(y,x,z), E[i]=z;
sort(E+1,E+m+1);
E[m+1]=-1, x=0, y=m+1; //trick
while (x <= y)
{
mid=(x+y)>>1;
if (spfa())
y=mid-1;
else
x=mid+1;
}
printf("%d
",E[min(y+1,m+1)]);
return 0;
}
poj3666【Making the Grade】
题意是求把一个数列变成不增或不降的最小代价,但数据好像只要求变成不降的代价。
首先,一个显然的结论就是在代价不变的前提下,一定可以满足修改后的数列最大值是原数列中的某一个数。想到这一点就可以做了。
设 (f[i][j]) 表示已经处理好前 (i) 位且第 (i) 位为 (j) 的最小代价。因为第 (i) 位已经是 (j) ,所以 (f[i][j]) 可以从 (f[i-1][k]) ((k <= j)) 转移转移过来。但这样是 (O(N^3)) ,所以要记 (f[i-1]) 的最小值。因为 (f[i][j]) 是递推的,所以可以边推边记。
然后就是 (j) 可能很大,但 (n) 很小,所以要离散。
#include <bits/stdc++.h>
using namespace std;
#define db double
#define ll long long
#define RG register
inline int gi()
{
RG int ret; RG bool flag; RG char ch;
ret=0, flag=true, ch=getchar();
while (ch < '0' || ch > '9')
ch == '-' ? flag=false : 0, ch=getchar();
while (ch >= '0' && ch <= '9')
ret=(ret<<3)+(ret<<1)+ch-'0', ch=getchar();
return flag ? ret : -ret;
}
const db pi = acos(-1.0);
const int N = 2005, inf = 1<<30;
int a[N],b[N];
ll f[N][N];
int main()
{
freopen("Making_the_Grade.in","r",stdin);
freopen("Making_the_Grade.out","w",stdout);
int n,i,j;
ll m,ans;
n=gi();
for (i=1; i<=n; ++i)
b[i]=a[i]=gi();
sort(b+1,b+n+1);
for (i=1; i<=n; ++i)
{
m=inf;
for (j=1; j<=n; ++j)
m=min(m,f[i-1][j]), f[i][j]=m+abs(b[j]-a[i]);
}
ans=inf;
for (i=1; i<=n; ++i)
ans=min(ans,f[n][i]);
printf("%lld
",ans);
return 0;
}
poj3616【Milking Time】
题意是给你一些区间,每个区间有一些价值,若两个区间间隔不超过 (r),这两个区间不能同时选。
这道题的转移和最长上升子序列差不多,也是从前面的合法状态中取 (max) 加上当前的价值。要注意的是每个点的初始值是自身的价值而不是0。
#include <bits/stdc++.h>
using namespace std;
#define db double
#define ll long long
#define RG register
inline int gi()
{
RG int ret; RG bool flag; RG char ch;
ret=0, flag=true, ch=getchar();
while (ch < '0' || ch > '9')
ch == '-' ? flag=false : 0, ch=getchar();
while (ch >= '0' && ch <= '9')
ret=(ret<<3)+(ret<<1)+ch-'0', ch=getchar();
return flag ? ret : -ret;
}
const db pi = acos(-1.0);
const int N = 1005, inf = 1<<30;
int f[N];
struct range
{
int l,r,val;
inline bool operator <(const range &R) const { return l < R.l; }
}ra[N];
int main()
{
freopen("Milking_Time.in","r",stdin);
freopen("Milking_Time.out","w",stdout);
RG int n,m,r,i,j,ans;
n=gi(), m=gi(), r=gi();
for (i=1; i<=m; ++i)
ra[i].l=gi(), ra[i].r=gi(), ra[i].val=gi();
sort(ra+1,ra+m+1);
for (i=1; i<=m; ++i)
f[i]=ra[i].val;
ans=f[1];
for (i=2; i<=m; ++i)
{
for (j=1; j<i; ++j)
if (ra[j].r <= ra[i].l-r)
f[i]=max(f[i],f[j]+ra[i].val);
ans=max(ans,f[i]);
}
printf("%d
",ans);
return 0;
}
poj3280【Cheapest Palindrome】
题意是给你一个字符串,求把他变成回文串的最小代价。修改不同的字符有不同的代价。
做这到题的时候我不知道在干嘛,完全没有思路,一些很显然的东西也没有看出来。
首先,对于一个字符,加入和删除本质上是一样的。因为对于一个串,两端不同的话,既可以删除一个不同的字符,又可以添加一个相同的字符。
然后,就可以像普通的区间dp一样更新了。
设 (f[i][j]) 表示从i到j这一段变成回文的最小代价。$f[i][j] $可以从 (f[i+1][j]) 转移过来,也可以从 (f[i][j-1]) 转移过来。这两个转移是不需要条件的,还有一个就是当 (a_{i}==a_{j}) 时,(f[i][j]) 可以从 (f[i+1][j-1]) 转移过来。
然后就是拓扑序的问题,这道题更新 (f[i][j]) 要用到 (f[i+1]) 和 (f[i][j-1]) 的值,所以 (i) 要从大往小枚举,(j) 要从小往大枚举。
#include <bits/stdc++.h>
using namespace std;
#define db double
#define ll long long
#define RG register
inline int gi()
{
RG int ret; RG bool flag; RG char ch;
ret=0, flag=true, ch=getchar();
while (ch < '0' || ch > '9')
ch == '-' ? flag=false : 0, ch=getchar();
while (ch >= '0' && ch <= '9')
ret=(ret<<3)+(ret<<1)+ch-'0', ch=getchar();
return flag ? ret : -ret;
}
const db pi = acos(-1.0);
const int N = 2005, inf = 1<<30;
int cst[30],f[N][N];
char a[N];
int main()
{
freopen("Cheapest_Palindrome.in","r",stdin);
freopen("Cheapest_Palindrome.out","w",stdout);
int n,m,i,j;
char s;
n=gi(), m=gi();
scanf("%s",a+1);
for (i=1; i<=n; ++i)
{
s=getchar();
while (s < 'a' || s > 'z')
s=getchar();
cst[s-'a']=min(gi(),gi());
}
for (i=m-1; i; --i)
for (j=i+1; j<=m; ++j)
{
f[i][j]=min(f[i+1][j]+cst[a[i]-'a'],f[i][j-1]+cst[a[j]-'a']);
if (a[i] == a[j])
f[i][j]=min(f[i][j],f[i+1][j-1]);
}
printf("%d
",f[1][m]);
return 0;
}