\(2019\) 暑期刷题记录 \(2\)(基本算法专题)
\(by~~wch\)
\(BZOJ~1958~Strange~Towers~of~Hanoi\) (动态规划,递推)
题目大意:
求有 \(n\) 个盘子和\(4\)座塔的汉诺塔问题。
\(solotion:\)
- 首先需要参考一下三座塔的汉诺塔问题:\(g[i]=2*g[i-1]+1\)
- 然后我们将四座塔转换为三座塔问题:将前\(i\)个盘子一道2号塔,剩下的\(n-i\) 个为三汉诺问题移到4号塔,再将前\(i\) 个用四汉诺塔问题移到4号塔。
- \(f[n]=min_{1\leq i < n}{2*f[i]+g[n-i]}\)
\(code:\)
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#define ll long long
#define db double
#define rg register int
using namespace std;
int n=12;
int g[13],f[13];
inline int qr(){
register char ch; register bool sign=0; rg res=0;
while(!isdigit(ch=getchar()))if(ch=='-')sign=1;
while(isdigit(ch))res=res*10+(ch^48),ch=getchar();
if(sign)return -res; else return res;
}
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
for(rg i=1;i<=n;++i) g[i]=2*g[i-1]+1;
for(rg i=1;i<=n;++i){
f[i]=1<<30;
for(rg j=0;j<i;++j)
f[i]=min(f[i],f[j]*2+g[i-j]);
}
for(rg i=1;i<=n;++i)
printf("%d\n",f[i]);
return 0;
}
$BZOJ ~1218 ~ $激光炸弹(二维前缀和)
\(solotion:\)
- 比较明显的二维前缀和问题
- 就是这一题有点卡常,如果暴力转一维前缀和会超时
- 我们需要自己寻找每个点前缀和的关系:
- \(s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j]\)
\(code:\)
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<ctime>
#include<queue>
#include<vector>
#include<map>
#include<set>
#define ll long long
#define db double
#define inf 0x7fffffff
#define rg register int
using namespace std;
int n,m,ans,a,b,c;
int f[5003][5003];
inline int qr(){
char ch; //int sign=1;
while((ch=getchar())<'0'||ch>'9');
// if(ch=='-')sign=-1;
int res=ch^48;
while((ch=getchar())>='0'&&ch<='9')
res=res*10+(ch^48);
return res;
}
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
n=qr(),m=qr();
for(rg i=1;i<=n;++i){
a=qr()+1,b=qr()+1,c=qr();
f[a][b]=c;
}
for(rg i=1;i<=5001;++i)
for(rg j=1;j<=5001;++j)
f[i][j]+=f[i-1][j]+f[i][j-1]-f[i-1][j-1];
for(rg i=m;i<=5001;++i){
for(rg j=m;j<=5001;++j){
ans=max(ans,f[i][j]-f[i-m][j]-f[i][j-m]+f[i-m][j-m]);
}
}printf("%d\n",ans);
return 0;
}
\(POJ~3263~Tallest~Cow\)(前缀和,贪心)
\(solotion:\)
- 很久以前就做过,一道比较有意思的前缀和做法
- 我们首先可以贪心想到牛的身高每次只减1,然后求出最少的减少次数
- 每头牛的身高减少次数是确定的,我们对于每一对可以互相看见的牛,他们中间的牛的身高都要减少一次
- 所以我们在\(A_i\) 处加一,在\(B_i+1\)处减一然后求前缀和就可以知道每一头牛的减少次数
- 然后这题不用担心每头牛都要比两端点的牛矮,所有区间一定是包含关系,而不会相交
\(code:\)
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#define ll long long
#define db double
#define inf 0x7fffffff
#define rg register int
using namespace std;
int n,m,h,q,a,b;
int s[10001];
bool use[10001][10001];
inline int qr(){
char ch;
while((ch=getchar())<'0'||ch>'9');
int res=ch^48;
while((ch=getchar())>='0'&&ch<='9')
res=res*10+(ch^48);
return res;
}
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
n=qr(),m=qr(),h=qr(),q=qr();
while(q--){
a=qr(),b=qr();
if(a>b)swap(a,b);
if(use[a][b])
continue;
use[a][b]=1;
--s[a+1];
++s[b];
}
for(rg i=1;i<=n;++i){
s[i]=s[i]+s[i-1];
printf("%d\n",h+s[i]);
}
return 0;
}
\(POJ~1845~Sumdiv\)(约数和定理)(分治)
题目大意:
求\(A^B\) 的所有约数之和
\(solotion:\)
- 首先要知道约数和定理:\((1+p_1+p_1^2+...+p_1^{c_1})*(1+p_2+p_2^2+...+p_2^{c_2})*...*(1+p_k+p_k^2+...+p_k^{c_k})\)
- 然后本题就比较好做了,\(A^B\) 的所有约数之和就是让上式中所有\(c_i\) 乘上\(B\) 即可
- 然后括号里面的东西直接用分治求即可
\(code:\)
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#define ll long long
#define db double
#define rg register int
using namespace std;
const int mod=9901;
int n,m,ans;
inline int qr(){
register char ch; register bool sign=0; rg res=0;
while(!isdigit(ch=getchar()))if(ch=='-')sign=1;
while(isdigit(ch))res=res*10+(ch^48),ch=getchar();
if(sign)return -res; else return res;
}
inline int ksm(ll x,int y){
ll res=1;
while(y){
if(y&1)res=res*x%mod;
x=x*x%mod; y>>=1;
}return res;
}
inline int ask(int x,int y){
if(y==1)return x;
if(y&1) return ((ll)ask(x,y>>1)*(ksm(x,y>>1)+1)%mod+ksm(x,y)%mod)%mod;
else return (ll)ask(x,y>>1)*(ksm(x,y>>1)+1)%mod;
}
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
while(~scanf("%d%d",&n,&m)){
if(m==0){puts("1");continue;}
ans=1;
for(rg i=2;i*i<=n;++i){
if(n%i)continue;
rg x=0; while(!(n%i))++x,n/=i;
ans=(ll)ans*(ask(i%mod,x*m)+1)%mod;
}if(n!=1)ans=(ll)ans*(ask(n%mod,m)+1)%mod;
printf("%d\n",ans);
}
return 0;
}
\(POJ~2018~Best~Cow~Fences\)(二分答案构造新权值)
单独的一篇题解
\(POJ~3889~Fractal~Streets\)(模拟)
单独的一篇题解
\(Codeforces~670C~Cinema\)(离散化)
\(solotion:\)
- 很傻的一道题目,直接离散化求出每种语言对应的人
- 然后枚举电影,求出其权值,最大的那个就是答案
\(code:\)
#include<stdio.h>
#include<map>
using namespace std;
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
int m,ple=0,sat=0,fi=1,sci[200005],bn[200005],cn;
map<int,int> mp;
for(int i=0;i<n;i++)
{
scanf("%d",&sci[i]);
mp[sci[i]]++;
}
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%d",&bn[i]);
}
for(int i=1;i<=m;i++)
{
scanf("%d",&cn);
if(mp[bn[i]]>ple)
{
ple=mp[bn[i]];
sat=mp[cn];
fi=i;
}
else if(mp[bn[i]]==ple&&mp[cn]>sat)
{
sat=mp[cn];
fi=i;
}
}
printf("%d\n",fi);
}
return 0;
}
\(POJ~3784~Running~Media\)(动态中位数)
题目大意:
依次读入数列,求每一个数加入时的中位数。
\(solotion:\)
- 直接用小根堆加大根堆维护一下就行
- 每次记录两边总数,判断加到哪边即可
\(code:\)
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<iostream>
using namespace std;
int n,t,x,a,b,cnt;
inline void read(int &x){
char ch=getchar();
char c=ch;
x=0;
while(ch<'0' || ch>'9') {
c=ch;
ch=getchar();
}
while(ch>='0' && ch<='9'){
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
if(c== '-') x=-x;
}
std::priority_queue <int> lq,sq;
int main(){
read(t);
while(t--){
read(cnt);
read(n);
while(!lq.empty()) lq.pop();
while(!sq.empty()) sq.pop();
cout<<cnt<<' '<<((n+1)/2)<<endl;
for(int i = 1;i <= n;++ i){
read(x);
lq.push(x);
sq.push(-x);
if(i%2==0) continue;
while(lq.top()!=-sq.top()){
a=lq.top();
lq.pop();
b=-sq.top();
sq.pop();
sq.push(-a);
lq.push(b);
}
cout<<lq.top()<<' ';
if(((i+1)/2)%10==0) puts("");
else if((n%2==1 && i==n) || (n%2==0 && i==n-1)) puts("");
}
}
return 0;
}
\(POJ~2299~Ultra-QuickSort\)(逆序对)
题目大意:
求一个序列里的逆序对。
\(solotion:\)
- 没想会在基本算法里碰见这个,挺怀念的。
- 我们用树状数组维护一下(需要离散化),和最长上升子序列里一个板子
\(POJ~3614~Sunscreen\)(贪心,优先队列)
\(solotion:\)
- 这个题目比较容易想到贪心
- 我们将奶牛和防晒霜分开,都以阳光强度(最小值)排序
- 然后我们可以想到枚举防晒霜
- 用一个优先队列依次存下当前防晒霜能保护的牛
- 然后我们用优先队列求出右端点(最大阳光强度)最小的牛用这个防晒霜
- 这样肯定比用最大阳光强度的好,因为我们将最大阳光强度大的留给了后面的防晒霜。
- 而最小阳光强度在枚举时得到了保障(我们是依次加入防晒霜能保护的牛的)。
\(code:\)
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#define ll long long
#define db double
#define rg register int
using namespace std;
int n,m,ans;
struct su{
int x,y;
inline bool operator <(const su &z)const{
return x<z.x;
}
}a[2505],b[2505];
struct pi{
int x,y;
inline bool operator <(const pi &z)const{
return y>z.y;
}
};
priority_queue<pi> q;
inline int qr(){
register char ch; register bool sign=0; rg res=0;
while(!isdigit(ch=getchar()))if(ch=='-')sign=1;
while(isdigit(ch))res=res*10+(ch^48),ch=getchar();
if(sign)return -res; else return res;
}
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
n=qr(); m=qr();
for(rg i=1;i<=n;++i)
a[i].x=qr(),a[i].y=qr();
sort(a+1,a+n+1);
for(rg i=1;i<=m;++i)
b[i].x=qr(),b[i].y=qr();
sort(b+1,b+m+1);
for(rg i=1,l=1;i<=m;++i){
while(l<=n&&a[l].x<=b[i].x){
pi x; x.x=l; x.y=a[l].y; q.push(x); ++l;
}
while(!q.empty()&&q.top().y<b[i].x)q.pop();
while(!q.empty()&&b[i].y){
++ans; --b[i].y; q.pop();
}
}printf("%d\n",ans);
return 0;
}
\(POJ~3190~Stall~Reservation\)(前缀和,优先队列)
\(solotion:\)
- 这道题的最小畜栏数是确定的
- 我们直接用差分约束前缀和的方法即可得出答案
- 但是这道还需要我们输出畜栏序号,这就需要我们动态维护那几个畜栏没有用过
- 我们用优先队列优化,按左端点枚举每一头牛,然后用优先队列记录它的右端点以及它用的哪一个畜栏,(畜栏的使用状态也需要用一个优先队列维护(因为我们要最小化畜栏数目))
- 然后在枚举过程中还要对优先队列进行取出操作,将用完的畜栏记录下来,病房会优先队列里去
\(code:\)
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#define ll long long
#define db double
#define rg register int
using namespace std;
int n,tt,ans;
int as[50005];
struct su{
int l,r,id;
inline bool operator <(const su &x)const{
return l<x.l;
}
}a[50005];
struct pi{
int x,y;
inline bool operator <(const pi &z)const{
return x>z.x;
}
};
priority_queue<pi> q;
priority_queue<int, vector<int>, greater<int> >p;
inline int qr(){
register char ch; register bool sign=0; rg res=0;
while(!isdigit(ch=getchar()))if(ch=='-')sign=1;
while(isdigit(ch))res=res*10+(ch^48),ch=getchar();
if(sign)return -res; else return res;
}
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
n=qr();
for(rg i=1;i<=n;++i)
a[i].l=qr(),a[i].r=qr(),a[i].id=i,p.push(i);
sort(a+1,a+n+1);
for(rg i=1;i<=n;++i){
while(!q.empty()&&q.top().x<a[i].l){
p.push(q.top().y); q.pop();
}as[a[i].id]=p.top(); p.pop();
pi x; x.x=a[i].r; x.y=as[a[i].id]; q.push(x);
ans=max(ans,as[a[i].id]);
}
printf("%d\n",ans);
for(rg i=1;i<=n;++i)
printf("%d\n",as[i]);
return 0;
}