contest-hunter Chapter 1,2,3

---恢复内容开始---

# 基本算法 ---

龟速乘

long long mul(long long a,long long b,long long p)
{
	long long ans=0; 
	for( ; b ; b>>=1)
	{
		if(b&1) ans=(ans+a)%p;
		a=a*2%p;
	}
	return ans;
}

分治

求A^B的所有约数之和 mod 9901:

将A分解质因数,原问题化简为求(1+p+\(p^2\)+...+\(p^(B*C)\)),其中p为A的所有因数,C为其次数

vector <node> v;
inline void pre()
{
	for(res i=2 ; i*i<=A ; ++i)
	{
		if(A%i==0)
		{
			LL cnt=0;
			while(A%i==0) A/=i,++cnt;
			v.push_back(node{i,cnt});
		}
	}
	if(A!=1) v.push_back(node{A,1});
}

inline LL qpow(LL a,LL b)
{
	LL t=1;
	a%=mod;
	while(b)
	{
		if(b&1) t=t*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return t;
}

inline LL calc(LL p,LL c)
{
	if(!p) return 0;
	if(!c) return 1;
	if(c&1) return (1+qpow(p,(c+1)/2))*calc(p,(c-1)/2)%mod;
	return (qpow(p,c)+(1+qpow(p,c/2)) * calc(p,c/2-1)%mod )%mod;
}

int main()
{
	scanf("%lld %lld",&A,&B);
	pre();
	for(res i=0,z=v.size() ; i<z ; ++i)
	{
		LL p=v[i].x,c=v[i].y*B;
		ans=(ans*calc(p,c))%mod;
	}
	printf("%lld\n",ans);
	return 0;
}

差分

IncDec Sequence

给定一个长度为 \(n(n\leqslant10^5 )\) 的数列 \({a_1,a_2,…,a_n}\),每次可以选择一个区间 \([l,r]\),使下标在这个区间内的数都加一或者都减一。
求至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列可能有多少种。

求出差分数组\(b[i]=a[i]-a[i-1]\),则每次操作都可以转化为令\(b[l]+1,b[r+1]-1\)\(b[l]-1,b[r+1]+1\),最后零b[2]~b[n]都为0

这时所有操作分4种情况
1.选\(b[i]和b[j]\), \((2\leqslant i,j \leqslant n)\)
2.选\(b[1]和b[j]\), \((2\leqslant j \leqslant n)\)
3.选\(b[1]和b[n+1]\)
4.选\(b[i]和b[n+1]\), \((2\leqslant i \leqslant n)\)(显然没有用)
显然,优先选择第一类操作,然后进行第2或者第3类操作,则最终操作次数即为max(p,q), 而最后\(|p-q|\)次2或3的操作,能产生\(|p-q|\)+1种不同的\(b[1]\)

LL a[N],b[N],pos,neg,n;

signed main()
{			
	n=read();
	for(res i=1 ; i<=n ; ++i)
	{
		a[i]=read();
		b[i]=a[i]-a[i-1];
	}
	for(res i=2 ; i<=n ; ++i)
	{
		if(b[i]>0) pos+=b[i];
		else neg-=b[i];
	}
	int ans1=max(pos,neg);
	int ans2=abs(pos-neg)+1;
	cout<<ans1<<endl<<ans2<<endl;
	return 0;
}

基本数据结构

优先队列

Sequence
给定M个长度为N的序列,从每个序列中任取一个数求和,可以构成\(N^M\)个和,求其中最小的N个和

对于M=2的问题,可以建立一个小根堆,两个指针移动
则对于原问题,可以先求出前2个序列的最小的前N隔阂,再把这N个和作为一个序列,与第3个序列求...以此类推,总复杂度\(O(MNlogN)\)

const int N=2050+10;
typedef long long LL;
int m,n,a[N],b[N],t[N];

struct node{
    int x,y,z;
    bool operator<(const node &n2)const
	{return a[x]+b[y]>a[n2.x]+b[n2.y];}
}; 
//z表示第一个指针能否移动
il void work()
{
    priority_queue<node> q;
    q.push(node{1,1,false});
    for(res i=1 ; i<=n ; ++i)
    {
        node now=q.top(); q.pop();
        int x=now.x,y=now.y,z=now.z;
        t[i]=a[x]+b[y];
        q.push(node{x,y+1,true});
        if(z==false) q.push(node{x+1,y,false});
    }
    for(res i=1 ; i<=n ; ++i) a[i]=t[i];
}

int main()
{
	m=read(); n=read();
        for(res i=1 ; i<=n ; ++i) a[i]=read();
        sort(a+1,a+n+1);
        --m;
        while(m--)
        {
            for(res i=1 ; i<=n ; ++i) b[i]=read();
            sort(b+1,b+n+1);
            work();
        }
        for(res i=1 ; i<n ; ++i) printf("%d ",a[i]);
        printf("%d\n",a[n]);
}

数据备份
在n个点中,你需要选择K对点,使得每一对点之间的距离之和(总距离)尽可能小(n>2k),并且这K对点都是互不相同的

问题转化为, 在差分数组中找k个数,满足k个数之和最小且互不相邻
设差分数组为b[], 其中最小的数为b[i],显然最优解必定是以下其中一种
1.包含b[i]以及除b[i-1]和b[i+1]的数
2.包含b[i-1]和b[i+1]以及除b[i],b[i-2],b[i+2]
从这一点扩展, 可以先取b[i],并以b[i-1]+b[i+1]-b[i]替换,
然后在新数列中继续重复k-1次得到最后结果
这样若b[i]不属于最优解,则b[i-1]+b[i+1]-b[i]必定被选,满足了上述第二种情况
更具体做法是, 将原差分数组每个值插入堆, 并将数组以链表串起来
每次取堆顶最小值更新答案,并删除该值,
设最小值编号为i, 那么再插入b[ pre[i] ]+b[ nxt[i] ]-b[i], 并更新链表
重复k次即得最优解

for(res i=1 ; i<=n ; ++i) 
        a[i]=read();
    for(res i=1 ; i<n ; ++i)
    {
        b[i]=a[i+1]-a[i];
        q.push(node{b[i],i});
        pre[i]=i-1; nxt[i]=i+1;
    }
    b[0]=b[n]=1e9+7;
    // pre[]
    long long ans=0;
    while(k--)
    {
        while(q.size() && vis[q.top().id]) q.pop();
        node now=q.top(); q.pop();
        int t=now.id;
        ans+=now.v;
        b[t]=b[pre[t]]+b[nxt[t]]-b[t];
        vis[pre[t]]=vis[nxt[t]]=1;
        pre[t]=pre[pre[t]];
        nxt[t]=nxt[nxt[t]];
        nxt[pre[t]]=t;
        pre[nxt[t]]=t;
        q.push(node{b[t],t});
    }

Huffman树

荷马史诗
。。。

搜索

剪枝

常见剪枝方法
1.优化搜索顺序
2.排除等效冗余 : 从当前节点沿着某几条不同的分支到达的子树可能是等效的,(详见sticks)
3.可行性剪枝 : 即提前发现当前分支是不合法的,直接回溯
4.最优性剪枝 : 代价已经超过了最优解
5.记忆化


小木棍
乔治拿来一组等长的木棒,将它们随机的砍掉,得到若干根小木棍,并使每一节小棍的长度都不超过50个单位。
然后他又想把这些木棍拼接起来,恢复到裁剪前的状态,但他忘记了初始时有多少木棒以及木棒的初始长度。
请你设计一个程序,帮助乔治计算木棒的可能最小长度,每一节木棍的长度都用大于零的整数表示。

void dfs(int now,int last,int rest)
{
    if(rest==0)
    {	
        int i;
        if(now==m) {ok=1; return ;}
        for(i=1 ; i<=n ; i++) if(!used[i]) break;
        used[i]=1; dfs(now+1,i,len-a[i]);
        used[i]=0; if(ok) return ;
    }
    
    for(res i=last+1 ; i<=n ; i++)
    {
        if(!used[i]&&a[i]<=rest)
        {
            used[i]=1;	dfs(now,i,rest-a[i]);	used[i]=0;
            if(ok)	return ;
            while(a[i+1]==a[i]) i++;//
            if(rest==len||rest==a[i]) return ; 
        }
        if(i==n) return ; 
    }
}

/*剪枝:
1.把小木棍从大到小排序,先拼大的
2.从小到大枚举原始木块长度len,成立了就直接输出答案。范围:最短的木棍到所有木棍
长度之和的一半,且len需要整除sum
3.当一个木棍不能拼时,和它长度相等的木棍显然也不能拼
4.枚举下一个要拼的木棍时只需从上一个用过的木块的下一个开始枚举
****5.如果当前长棍剩余的未拼长度等于当前正在试着拼的木块的长度或者等于原始长度(即
刚开始拼),继续拼下去却失败了,则直接回溯,结束枚举。 
*/ 
int main()
{

    while(scanf("%d",&n)==1 && n)
    {
    	sum=0;ok=0;
    	memset(used,0,sizeof(used));
    	memset(a,0,sizeof(a));
    	for(res i=1 ; i<=n ; i++)
    	{
      	  a[i]=read();
      	  sum+=a[i];
    	}
    	sort(a+1,a+n+1,cmp);
    	for(len=a[1] ; len<=sum/2+1 ; len++)
    	{
    	    if(sum%len!=0) continue;
    	    used[1]=1; m=sum/len;
    	    ok=0;
     		dfs(1,1,len-a[1]);
     		used[1]=0;
            if(ok) 
            {
      	  		printf("%d\n",len);
      	  		break;
            }
    	}
    	if(!ok) printf("%d\n",sum);
    }
    return 0;
}

生日蛋糕
要制作一个体积为Nπ的M层生日蛋糕,每层都是一个圆柱体.
设从下往上数第i\((1 \leqslant i \leqslant M)\)层蛋糕是半径为Ri, 高度为Hi的圆柱。当i<M时,要求\(R_i>R_{i+1},R_i>R_{i+1}且H_i>H_{i+1},H_i>H_{i+1}\)
由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积Q最小。
令Q= Sπ ,请编程对给出的N和M,找出蛋糕的制作方案(适当的Ri和Hi的值),使S最小. (除Q外,以上所有数据皆为正整数)

void dfs(int now,int s,int v,int r,int h)//从上往下数第now层 
{
	if(now==0)
	{
		if(s<ans&&v==n) ans=s;
		return ;	
	}
	if(s+mins[now-1]>ans||v+minv[now-1]>n) return ;
/*
     *  2*r*h=S
     *  r*r*h=V
     *  => V*2/r >= S
     *  S + sumS >= ans => return;
*/
	if(2*(n-v)/r+s>ans) return;
	for(res i=r-1 ; i>=now ; i--)//倒着搜
	{
		if(now==m) s=i*i;
		int maxH=min((n-v-minv[now-1])/(i*i),h-1);
		for(res j=maxH ; j>=now ; j--)
			dfs(now-1,s+i*j*2,v+i*i*j,i,j);	
	}
}

int main()
{
	n=read(); m=read();//体积,层数 
	for(res i=1 ; i<=m ; i++)
		mins[i]=mins[i-1]+i*i*2,
		minv[i]=minv[i-1]+i*i*i;
	dfs(m,0,0,100,100);
	if(ans==1e9) ans=0;
	printf("%d\n",ans);	
	return 0;
}

迭代加深

Addition Chains
一个与 n 有关的整数加成序列\((a_0,a_1,...,a_m)\)满足一下四个条件
1.\(a0=1\)
2.\(am=n\)
3.\(a_0<a_1<a_2<...<a_m\)
4.对于每一个 k\((1≤k≤m)\)都存在有两个整数 i 和 j (\(0≤i,j≤k−1\), i和j可以相等) ,使得\(a_k=a_i+a_j\)
​你的任务是:给定一个整数 nn ,找出符合上述四个条件的长度最小的整数加成序列。如果有多个满足要求的答案,只需要输出任意一个解即可。

int a[N],n,dep;

inline void init()
{
    memset(a,0,sizeof(a));
    a[1]=1; a[2]=2; dep=0;
    int tmp(1);
    while(tmp<n)
    {
        tmp<<=1;
        dep++;
    }
}

inline bool dfs(int x)
{
    if(x>dep) {
        if(a[dep]==n) return true;
        return false;
    }
    if(a[x-1]<<(dep-x+1)<n) return false;
    for(res i=x-1 ; i>=1 ; i--)
        for(res j=i ; j>=1 ; j--)
        {
        	int sum=a[i]+a[j]; 
        	if(sum<=n)
        	{
        		if(sum<=a[x-1]) return false;
        		a[x]=sum;
        		if(dfs(x+1)) return true;
            }
        }
    return false;
}

int main()
{
    a[1]=1; a[2]=2;
    while(scanf("%d",&n) && n)
    {
        init();
        while(!dfs(3)) dep++;
        printf("%d",a[1]);
        for(res i=2 ; i<=dep ; i++) printf(" %d",a[i]);
        printf("\n");
        
    }
    return 0;
}

双向搜索

送礼物
作为惩罚,GY被遣送去帮助某神牛给女生送礼物(GY:貌似是个好差事)但是在GY看到礼物之后,他就不这么认为了。某神牛有N个礼物,且异常沉重,但是GY的力气也异常的大(-_-b),他一次可以搬动重量和在w(w<=2^31-1)以下的任意多个物品。GY希望一次搬掉尽量重的一些物品,请你告诉他在他的力气范围内一次性能搬动的最大重量是多少。

1.将礼物分成两半,首先在前一半中暴力搜索出所有情况,记录,排序,去重,然后搜后一半礼物,对于后一半每一个可以达到的重量值t,都在前一半搜过的情况中二分查找W-t中数值最大的,然后更新答案,
2.优化搜索顺序,将礼物重量降序排列后再分半,搜索
3.选取适当的折半划分点,据lyd大佬所说,在N/2+2处搜索速度最快

void dfs1(int x,unsigned int sum)
{
	if(x==half) 
    {
		a[++tot]=sum; 
        return ;
	}
	dfs1(x+1,sum);
	if(sum+g[x]<=W) dfs1(x+1,sum+g[x]);
}

inline void calc(unsigned int val)
{
	unsigned int rest=W-val;
	int l=1,r=tot;
	while(l<r)
	{
		int mid=(l+r+1)>>1;
		if(a[mid]<=rest) l=mid;
		else r=mid-1;
	}
	ans=max(ans,val+a[l]);
}

void dfs2(int x,unsigned int sum)
{
	if(x==n+1) 
    {
		calc(sum); 
		return;
	}
	dfs2(x+1,sum);
	if(sum+g[x]<=W) dfs2(x+1,sum+g[x]);
}

int main()
{
//	W=read(); n=read(); 
	cin>>W>>n;
	for(res i=1 ; i<=n ; i++) 
        scanf("%d",&g[i]);//g[i]=read();
	sort(g+1,g+n+1); 
	reverse(g+1,g+n+1);
	half=n/2+3;
	dfs1(1,0);
	sort(a+1,a+tot+1);
	tot=unique(a+1,a+tot+1)-a-1;
	dfs2(half,0);
	printf("%lld\n",ans);
	return 0;
}

A* and IDA*

---恢复内容结束---

# 基本算法 ---

龟速乘

long long mul(long long a,long long b,long long p)
{
	long long ans=0; 
	for( ; b ; b>>=1)
	{
		if(b&1) ans=(ans+a)%p;
		a=a*2%p;
	}
	return ans;
}

分治

求A^B的所有约数之和 mod 9901:

将A分解质因数,原问题化简为求(1+p+\(p^2\)+...+\(p^(B*C)\)),其中p为A的所有因数,C为其次数

vector <node> v;
inline void pre()
{
	for(res i=2 ; i*i<=A ; ++i)
	{
		if(A%i==0)
		{
			LL cnt=0;
			while(A%i==0) A/=i,++cnt;
			v.push_back(node{i,cnt});
		}
	}
	if(A!=1) v.push_back(node{A,1});
}

inline LL qpow(LL a,LL b)
{
	LL t=1;
	a%=mod;
	while(b)
	{
		if(b&1) t=t*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return t;
}

inline LL calc(LL p,LL c)
{
	if(!p) return 0;
	if(!c) return 1;
	if(c&1) return (1+qpow(p,(c+1)/2))*calc(p,(c-1)/2)%mod;
	return (qpow(p,c)+(1+qpow(p,c/2)) * calc(p,c/2-1)%mod )%mod;
}

int main()
{
	scanf("%lld %lld",&A,&B);
	pre();
	for(res i=0,z=v.size() ; i<z ; ++i)
	{
		LL p=v[i].x,c=v[i].y*B;
		ans=(ans*calc(p,c))%mod;
	}
	printf("%lld\n",ans);
	return 0;
}

差分

IncDec Sequence

给定一个长度为 \(n(n\leqslant10^5 )\) 的数列 \({a_1,a_2,…,a_n}\),每次可以选择一个区间 \([l,r]\),使下标在这个区间内的数都加一或者都减一。
求至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列可能有多少种。

求出差分数组\(b[i]=a[i]-a[i-1]\),则每次操作都可以转化为令\(b[l]+1,b[r+1]-1\)\(b[l]-1,b[r+1]+1\),最后零b[2]~b[n]都为0

这时所有操作分4种情况
1.选\(b[i]和b[j]\), \((2\leqslant i,j \leqslant n)\)
2.选\(b[1]和b[j]\), \((2\leqslant j \leqslant n)\)
3.选\(b[1]和b[n+1]\)
4.选\(b[i]和b[n+1]\), \((2\leqslant i \leqslant n)\)(显然没有用)
显然,优先选择第一类操作,然后进行第2或者第3类操作,则最终操作次数即为max(p,q), 而最后\(|p-q|\)次2或3的操作,能产生\(|p-q|\)+1种不同的\(b[1]\)

LL a[N],b[N],pos,neg,n;

signed main()
{			
	n=read();
	for(res i=1 ; i<=n ; ++i)
	{
		a[i]=read();
		b[i]=a[i]-a[i-1];
	}
	for(res i=2 ; i<=n ; ++i)
	{
		if(b[i]>0) pos+=b[i];
		else neg-=b[i];
	}
	int ans1=max(pos,neg);
	int ans2=abs(pos-neg)+1;
	cout<<ans1<<endl<<ans2<<endl;
	return 0;
}

基本数据结构

优先队列

Sequence
给定M个长度为N的序列,从每个序列中任取一个数求和,可以构成\(N^M\)个和,求其中最小的N个和

对于M=2的问题,可以建立一个小根堆,两个指针移动
则对于原问题,可以先求出前2个序列的最小的前N隔阂,再把这N个和作为一个序列,与第3个序列求...以此类推,总复杂度\(O(MNlogN)\)

const int N=2050+10;
typedef long long LL;
int m,n,a[N],b[N],t[N];

struct node{
    int x,y,z;
    bool operator<(const node &n2)const
	{return a[x]+b[y]>a[n2.x]+b[n2.y];}
}; 
//z表示第一个指针能否移动
il void work()
{
    priority_queue<node> q;
    q.push(node{1,1,false});
    for(res i=1 ; i<=n ; ++i)
    {
        node now=q.top(); q.pop();
        int x=now.x,y=now.y,z=now.z;
        t[i]=a[x]+b[y];
        q.push(node{x,y+1,true});
        if(z==false) q.push(node{x+1,y,false});
    }
    for(res i=1 ; i<=n ; ++i) a[i]=t[i];
}

int main()
{
	m=read(); n=read();
        for(res i=1 ; i<=n ; ++i) a[i]=read();
        sort(a+1,a+n+1);
        --m;
        while(m--)
        {
            for(res i=1 ; i<=n ; ++i) b[i]=read();
            sort(b+1,b+n+1);
            work();
        }
        for(res i=1 ; i<n ; ++i) printf("%d ",a[i]);
        printf("%d\n",a[n]);
}

数据备份
在n个点中,你需要选择K对点,使得每一对点之间的距离之和(总距离)尽可能小(n>2k),并且这K对点都是互不相同的

问题转化为, 在差分数组中找k个数,满足k个数之和最小且互不相邻
设差分数组为b[], 其中最小的数为b[i],显然最优解必定是以下其中一种
1.包含b[i]以及除b[i-1]和b[i+1]的数
2.包含b[i-1]和b[i+1]以及除b[i],b[i-2],b[i+2]
从这一点扩展, 可以先取b[i],并以b[i-1]+b[i+1]-b[i]替换,
然后在新数列中继续重复k-1次得到最后结果
这样若b[i]不属于最优解,则b[i-1]+b[i+1]-b[i]必定被选,满足了上述第二种情况
更具体做法是, 将原差分数组每个值插入堆, 并将数组以链表串起来
每次取堆顶最小值更新答案,并删除该值,
设最小值编号为i, 那么再插入b[ pre[i] ]+b[ nxt[i] ]-b[i], 并更新链表
重复k次即得最优解

for(res i=1 ; i<=n ; ++i) 
        a[i]=read();
    for(res i=1 ; i<n ; ++i)
    {
        b[i]=a[i+1]-a[i];
        q.push(node{b[i],i});
        pre[i]=i-1; nxt[i]=i+1;
    }
    b[0]=b[n]=1e9+7;
    // pre[]
    long long ans=0;
    while(k--)
    {
        while(q.size() && vis[q.top().id]) q.pop();
        node now=q.top(); q.pop();
        int t=now.id;
        ans+=now.v;
        b[t]=b[pre[t]]+b[nxt[t]]-b[t];
        vis[pre[t]]=vis[nxt[t]]=1;
        pre[t]=pre[pre[t]];
        nxt[t]=nxt[nxt[t]];
        nxt[pre[t]]=t;
        pre[nxt[t]]=t;
        q.push(node{b[t],t});
    }

Huffman树

荷马史诗
。。。

搜索

剪枝

常见剪枝方法
1.优化搜索顺序
2.排除等效冗余 : 从当前节点沿着某几条不同的分支到达的子树可能是等效的,(详见sticks)
3.可行性剪枝 : 即提前发现当前分支是不合法的,直接回溯
4.最优性剪枝 : 代价已经超过了最优解
5.记忆化


小木棍
乔治拿来一组等长的木棒,将它们随机的砍掉,得到若干根小木棍,并使每一节小棍的长度都不超过50个单位。
然后他又想把这些木棍拼接起来,恢复到裁剪前的状态,但他忘记了初始时有多少木棒以及木棒的初始长度。
请你设计一个程序,帮助乔治计算木棒的可能最小长度,每一节木棍的长度都用大于零的整数表示。

void dfs(int now,int last,int rest)
{
    if(rest==0)
    {	
        int i;
        if(now==m) {ok=1; return ;}
        for(i=1 ; i<=n ; i++) if(!used[i]) break;
        used[i]=1; dfs(now+1,i,len-a[i]);
        used[i]=0; if(ok) return ;
    }
    
    for(res i=last+1 ; i<=n ; i++)
    {
        if(!used[i]&&a[i]<=rest)
        {
            used[i]=1;	dfs(now,i,rest-a[i]);	used[i]=0;
            if(ok)	return ;
            while(a[i+1]==a[i]) i++;//
            if(rest==len||rest==a[i]) return ; 
        }
        if(i==n) return ; 
    }
}

/*剪枝:
1.把小木棍从大到小排序,先拼大的
2.从小到大枚举原始木块长度len,成立了就直接输出答案。范围:最短的木棍到所有木棍
长度之和的一半,且len需要整除sum
3.当一个木棍不能拼时,和它长度相等的木棍显然也不能拼
4.枚举下一个要拼的木棍时只需从上一个用过的木块的下一个开始枚举
****5.如果当前长棍剩余的未拼长度等于当前正在试着拼的木块的长度或者等于原始长度(即
刚开始拼),继续拼下去却失败了,则直接回溯,结束枚举。 
*/ 
int main()
{

    while(scanf("%d",&n)==1 && n)
    {
    	sum=0;ok=0;
    	memset(used,0,sizeof(used));
    	memset(a,0,sizeof(a));
    	for(res i=1 ; i<=n ; i++)
    	{
      	  a[i]=read();
      	  sum+=a[i];
    	}
    	sort(a+1,a+n+1,cmp);
    	for(len=a[1] ; len<=sum/2+1 ; len++)
    	{
    	    if(sum%len!=0) continue;
    	    used[1]=1; m=sum/len;
    	    ok=0;
     		dfs(1,1,len-a[1]);
     		used[1]=0;
            if(ok) 
            {
      	  		printf("%d\n",len);
      	  		break;
            }
    	}
    	if(!ok) printf("%d\n",sum);
    }
    return 0;
}

生日蛋糕
要制作一个体积为Nπ的M层生日蛋糕,每层都是一个圆柱体.
设从下往上数第i\((1 \leqslant i \leqslant M)\)层蛋糕是半径为Ri, 高度为Hi的圆柱。当i<M时,要求\(R_i>R_{i+1},R_i>R_{i+1}且H_i>H_{i+1},H_i>H_{i+1}\)
由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积Q最小。
令Q= Sπ ,请编程对给出的N和M,找出蛋糕的制作方案(适当的Ri和Hi的值),使S最小. (除Q外,以上所有数据皆为正整数)

void dfs(int now,int s,int v,int r,int h)//从上往下数第now层 
{
	if(now==0)
	{
		if(s<ans&&v==n) ans=s;
		return ;	
	}
	if(s+mins[now-1]>ans||v+minv[now-1]>n) return ;
/*
     *  2*r*h=S
     *  r*r*h=V
     *  => V*2/r >= S
     *  S + sumS >= ans => return;
*/
	if(2*(n-v)/r+s>ans) return;
	for(res i=r-1 ; i>=now ; i--)//倒着搜
	{
		if(now==m) s=i*i;
		int maxH=min((n-v-minv[now-1])/(i*i),h-1);
		for(res j=maxH ; j>=now ; j--)
			dfs(now-1,s+i*j*2,v+i*i*j,i,j);	
	}
}

int main()
{
	n=read(); m=read();//体积,层数 
	for(res i=1 ; i<=m ; i++)
		mins[i]=mins[i-1]+i*i*2,
		minv[i]=minv[i-1]+i*i*i;
	dfs(m,0,0,100,100);
	if(ans==1e9) ans=0;
	printf("%d\n",ans);	
	return 0;
}

迭代加深

Addition Chains
一个与 n 有关的整数加成序列\((a_0,a_1,...,a_m)\)满足一下四个条件
1.\(a0=1\)
2.\(am=n\)
3.\(a_0<a_1<a_2<...<a_m\)
4.对于每一个 k\((1≤k≤m)\)都存在有两个整数 i 和 j (\(0≤i,j≤k−1\), i和j可以相等) ,使得\(a_k=a_i+a_j\)
​你的任务是:给定一个整数 nn ,找出符合上述四个条件的长度最小的整数加成序列。如果有多个满足要求的答案,只需要输出任意一个解即可。

int a[N],n,dep;

inline void init()
{
    memset(a,0,sizeof(a));
    a[1]=1; a[2]=2; dep=0;
    int tmp(1);
    while(tmp<n)
    {
        tmp<<=1;
        dep++;
    }
}

inline bool dfs(int x)
{
    if(x>dep) {
        if(a[dep]==n) return true;
        return false;
    }
    if(a[x-1]<<(dep-x+1)<n) return false;
    for(res i=x-1 ; i>=1 ; i--)
        for(res j=i ; j>=1 ; j--)
        {
        	int sum=a[i]+a[j]; 
        	if(sum<=n)
        	{
        		if(sum<=a[x-1]) return false;
        		a[x]=sum;
        		if(dfs(x+1)) return true;
            }
        }
    return false;
}

int main()
{
    a[1]=1; a[2]=2;
    while(scanf("%d",&n) && n)
    {
        init();
        while(!dfs(3)) dep++;
        printf("%d",a[1]);
        for(res i=2 ; i<=dep ; i++) printf(" %d",a[i]);
        printf("\n");
        
    }
    return 0;
}

双向搜索

送礼物
作为惩罚,GY被遣送去帮助某神牛给女生送礼物(GY:貌似是个好差事)但是在GY看到礼物之后,他就不这么认为了。某神牛有N个礼物,且异常沉重,但是GY的力气也异常的大(-_-b),他一次可以搬动重量和在w(w<=2^31-1)以下的任意多个物品。GY希望一次搬掉尽量重的一些物品,请你告诉他在他的力气范围内一次性能搬动的最大重量是多少。

1.将礼物分成两半,首先在前一半中暴力搜索出所有情况,记录,排序,去重,然后搜后一半礼物,对于后一半每一个可以达到的重量值t,都在前一半搜过的情况中二分查找W-t中数值最大的,然后更新答案,
2.优化搜索顺序,将礼物重量降序排列后再分半,搜索
3.选取适当的折半划分点,据lyd大佬所说,在N/2+2处搜索速度最快

void dfs1(int x,unsigned int sum)
{
	if(x==half) 
    {
		a[++tot]=sum; 
        return ;
	}
	dfs1(x+1,sum);
	if(sum+g[x]<=W) dfs1(x+1,sum+g[x]);
}

inline void calc(unsigned int val)
{
	unsigned int rest=W-val;
	int l=1,r=tot;
	while(l<r)
	{
		int mid=(l+r+1)>>1;
		if(a[mid]<=rest) l=mid;
		else r=mid-1;
	}
	ans=max(ans,val+a[l]);
}

void dfs2(int x,unsigned int sum)
{
	if(x==n+1) 
    {
		calc(sum); 
		return;
	}
	dfs2(x+1,sum);
	if(sum+g[x]<=W) dfs2(x+1,sum+g[x]);
}

int main()
{
//	W=read(); n=read(); 
	cin>>W>>n;
	for(res i=1 ; i<=n ; i++) 
        scanf("%d",&g[i]);//g[i]=read();
	sort(g+1,g+n+1); 
	reverse(g+1,g+n+1);
	half=n/2+3;
	dfs1(1,0);
	sort(a+1,a+tot+1);
	tot=unique(a+1,a+tot+1)-a-1;
	dfs2(half,0);
	printf("%lld\n",ans);
	return 0;
}

A* and IDA*

posted @ 2019-05-05 10:03  孑行  阅读(201)  评论(0编辑  收藏  举报