复习计划
最后时刻当然要有复习计划
总的来说,OI主要有一下算法

喂!!! 计算几何那些就算了吧.......
接下来我将跟着蓝书复习 NOIP CSP必备算法
Day1
今天复习基本算法
这些都是初赛内容,其实还比较简单啦
最短Hamilton路径
n这么小当然首选状压DP
f[i][j]表示i是一个二进制数,表示便利过的点,j是当前位置,f[i][j]存放当前状态的最短路径
f[i|(1<<k)][k]=f[i][j]+a[i]k
本来想压掉第二维的,发现不对...
反例很好想
因为你便利过同样几个点之后你并不知道你现在在哪
也就是说可能出现f[i]是从一个不与i相连的点转移过来的
这样就错啦
#include<bits/stdc++.h>
using namespace std;
int n,m,a[28][28],f[1<<21][28];//已走点和现在在j点
int main(){
cin>>n;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
cin>>a[i][j];
}
}
memset(f,127,sizeof(f));
f[1][0]=0;
for(int i=1;i<(1<<n);i++){
for(int j=0;j<n;j++){
if(i>>j&1)
for(int k=0;k<n;k++){
if(((i^(1<<j))>>k)&1)f[i][j]=min(f[i][j],f[i^(1<<j)][k]+a[k][j]);
}
}
}
cout<<f[(1<<n)-1][n-1]<<endl;
}
费解的开关
这题还是有点难度的
我们可以发现几个有用的性质:
1.每个点只会被操作一次(操作两次又回去了)
2.第一行固定,满足条件的方案只有1种
#include<bits/stdc++.h>
using namespace std;
const int inf = 0x3fffffff;
char g[6][6];
int dx[6] = {0,-1,0,1,0},dy[6] = {0,0,1,0,-1};
void turn(int x,int y){
for(int i = 0;i < 5;++i){
int nx = x + dx[i],ny = y + dy[i];
if(nx >= 0 && nx < 5 && ny >= 0 && ny < 5)
g[nx][ny] ^= 1;
}
}
int work(){
int ans = inf;
for(int i = 0;i < 1 << 5;++i){
int res = 0;
char cpy[6][6];
memcpy(cpy,g,sizeof(g));
for(int j = 0;j < 5;++j){
if(i >> j & 1){
res ++;
turn(0,j);
}
}
for(int y = 0;y < 4;++y){
for(int x = 0;x < 5;++x){
if(g[y][x] == '0'){
res ++ ;
turn(y + 1,x);
}
}
}
bool qwq = true;
for(int x = 0;x < 5;++x)
if(g[4][x] == '0'){
qwq = false;
break;
}
if(qwq) ans = min(ans,res);
memcpy(g,cpy,sizeof(g));
}
if(ans > 6) ans = -1;
return ans;
}
int main(void){
int T;
cin >> T;
while(T--){
for(int i = 0;i < 5;++i)
cin >> g[i];
cout << work() << endl;
}
return 0;
}
奇怪的汉诺塔问题
貌似是这本书最短的代码了
四个塔也就是比三个塔多一个嘛
没什么好怕的
我们知道三塔时递推式为f[i]=f[i-1]*2+1;
四塔无非就是多了一根柱子
可以考虑先把一部分移到D塔上
再按照三塔的方式把剩下的移到最终的柱子上
#include<bits/stdc++.h>
using namespace std;
int d[21],f[21];
int main(){
for(int i=1;i<=12;i++)d[i]=2*d[i-1]+1;
memset(f,127,sizeof(f));
f[0]=0;
for(int i=1;i<=12;i++)
for(int j=0;j<i;j++)
f[i]=min(f[i],f[j]+f[j]+d[i-j]);
for(int i=1;i<=12;i++)cout<<f[i]<<endl;
}
Day 2
sumdiv
这题还好啊只是要式子
显然a,b<=1e5不可能生成这个数
所以我们把底数a分解质因数
写成\(a=p1^c1*p2^c2*p3^c3*...*pn^cn\)
所以所有的约数和为 \((1+p1^1+p1^2+...+p1^{c1*b})*(1+p2^1+p2^2+...+p2^{c2*b})*...*(1+pn^1+pn^2+...+pn^{cn*b})\)
然后这就是个等比数列求和再相乘啦
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=9901;
int n,m,ans=1;
int quick(int b,int p){
int re=1;
while(p){
if(p&1)re=(re*b)%mod;
b=(b*b)%mod;
p>>=1;
}
return re;
}
void exgcd(int a,int b,int &x,int &y){
if(!b){x=1;return ;}
exgcd(b,a%b,y,x);
y-=a/b*x;
}
int getinv(int k){
int x,y;
exgcd(k,mod,x,y);
x=(x%mod+mod)%mod;
return x;
}
signed main(){
cin>>n>>m;
if(n==0){cout<<0;return 0;}
if(m==0){cout<<1;return 0;}
for(int i=2;i<=n;i++){
if(n%i==0){
int c=0;
while(n%i==0){
++c;
n/=i;
}
ans=(ans*((quick(i,c*m+1)-1)*getinv(i-1))%mod)%mod;
}
}
cout<<(ans%mod+mod)%mod;
}
/*
sum(pi^0+pi^1+pi^2+pi^3+...+pi^(m*ci))
=1*(1-q^n)/(1-q)
=(1-q^n)*inv(1-q)
*/
激光炸弹
二位前缀和板子题
但是有点要注意:
1.x和y都是从0开始
2.然后计算前缀和时要-1所以下标都+1
3.最后ij从0开始枚举,因为前缀和的套路是要减左端点减一(所以就是0啦)
4.此题卡空间所以只开一个sum数组
#include<bits/stdc++.h>
using namespace std;
int n,m,sum[5005][5005],ans;
int main(){
cin>>n>>m;
int x,y,z;
for(int i=1;i<=n;i++){
cin>>x>>y>>z;
sum[x+1][y+1]=z;
}
for(int i=1;i<=5001;i++){
for(int j=1;j<=5001;j++){
sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
}
}
for(int i=0;i<=5000-m;i++){
for(int j=0;j<=5000-m;j++){
ans=max(ans,sum[i+m][j+m]-sum[i+m][j]-sum[i][j+m]+sum[i][j]);
}
}
cout<<ans;
}
IncDec序列
新书上的题,我这没有...
看完题解的差分序列做法感觉很妙啊
差分完之后我们就只需要把每一个负数和正数配对消去
而剩下的正数or负数我们有两种选择,和一消或者和n+1消
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,a,la,x,y;
signed main(){
cin>>n>>la;
for(int i=2;i<=n;i++){
cin>>a;
int de=a-la;
la=a;
if(de>0)x+=de;
else y+=de;
}
cout<<max(x,-y)<<endl<<abs(x+y)+1;//因为p~q有abs(x+y)+1种数
}
Tallest Cow
看到这种区间修改我们应该想到差分
这题使用了差分的思想用d记录每个条件产生的影响
最后前缀和即可
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll n,m,i,j,k,p,h,c[10100],d[10100],q[1000005];
int main(){
cin>>n>>p>>h>>m;
for (i=1;i<=m;i++){
ll A,B;
cin>>A>>B;
if (A>B)
swap(A,B);
if (q[A]!=B)
q[A]=B,d[A+1]--,d[B]++;
}
for (i=1;i<=n;i++)
c[i]=c[i-1]+d[i];
for (i=1;i<=n;i++)
cout<<c[i]+h<<endl;
}
最佳牛围栏
小数二分模板
自信满满的敲了一个整数二分上去,WA了...
就直接二分答案平均值
再找块长大于m的有没有和>=0的
#include<bits/stdc++.h>
using namespace std;
int n,a[1000005],m;
double sum[1000005];
bool check(double k){
sum[0]=0;
for(int i=1;i<=n;i++){
sum[i]=sum[i-1]+a[i]-k;
}
double minn=0x7fffffff;
int j=m;
for(;j<=n;++j){
minn=min(sum[j-m],minn);
if(sum[j]>=minn)return 1;
}
return 0;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
double l=-1,r=100000;
while(r-l>=1e-8){
double mid=(l+r)*1.0/2;
if(check(mid))l=mid;
else r=mid;
}
cout<<(int)(r*1000);
}
Day 3
发现时间不够了...
改计划吧....
以后只会写一些重要的题目
那些太难的就不浪费时间了
如果复习完还有时间再写吧...

浙公网安备 33010602011771号