复习计划
最后时刻当然要有复习计划
总的来说,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号