task
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\n",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\n",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\n",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\n",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\n",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\n",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\n",f[1][m]);
return 0;
}