2015 10 09

Bzoj 幸福路径

令f[i][j][t]为从点i走到点j花2^t步的最大幸福值

那么有f[i][j][t]=max{f[i][k][t-1]+f[k][j][t-1]*p^(2^t)}

迭代多次即可得到答案的近似值

注意蚂蚁可能卡死在某个点不动,因此初始要将邻接矩阵清为-INF,然后每个点连一条边权为0的自环

此外注意下卡死时最后经过的那个点的权值会不会被统计这里可能会挂

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define WP 30
double f[105][105],re[105][105],w[105],p,ans;
int a,b;
int n,m,v0;
int main(){
	memset(f,-25,sizeof(f));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) {
		scanf("%lf",&w[i]); f[i][i]=0.0;
	}
	scanf("%d",&v0); scanf("%lf",&p);
	for(int i=1;i<=m;++i){
		scanf("%d%d",&a,&b); f[a][b]=w[b];
	}
	double df=p;
	for(int oo=1;oo<=WP;++oo){
       memset(re,-25,sizeof(re));
	   for(int k=1;k<=n;++k)
	     for(int i=1;i<=n;++i)
	       for(int j=1;j<=n;++j)
			re[i][j]=max(re[i][j],f[i][k]+f[k][j]*df);
		df*=df;
		memcpy(f,re,sizeof f);
	}
	for(int i=1;i<=n;++i) ans=max(ans,f[v0][i]);
	printf("%.1lf",ans*p+w[v0]);
}

Bzoj 采花:

原来想用莫队做,但是花的大小使得memset的清空效率非常低。网上有一种更实用的离线方法。

做这个题还是需要技巧的。

先将所有查询读入,按照右端点排序。

从1~n扫,维护pt[i]表示i向左第一个和a[i]相等的数字的位置,扫到i的时候实时更新树状数组:c[pt[pt[i]]+1]~c[pt[i]]区间+1(pt[i]!=0),与此同时,处理右端点与i重合的查询,此询问的答案就是这个询问的左端点在树状数组中的值(树状数组起区间修改单点查询的功能),至于为什么,画个图应该很容易知道。

PS:树状数组的区间修改单点查询的实现:

将原数组差分,令d[i]=c[i]-c[i-1],特别地,d[1]=c[1]。

那么区间[l,r]整体加上k的操作就可以简单地使用d[l]+=k;d[r+1]-=k来完成了。

此时c[n]=sigma(d[i])1<=i<=n,所以单点查询c[n]实际上就是在求d数组的[1~n]区间和。

AC代码:晦涩难懂
#include<cstdio>
#include<algorithm>
using namespace std;
#define Maxn 1000090
int Next[Maxn],pre[Maxn],a[Maxn],c[Maxn];
int n,ci,m,as[Maxn];
struct dd{
	int l,r,id;
}jie[Maxn];
int lowbit(int x){
	return x&(-x);
}
bool comp(const dd & a,const dd & b){
	return a.l<b.l;
}
void add(int x,int ad){
	for(;x<=n;x+=lowbit(x)) c[x]+=ad;
}
int Que(int x,int y){
	int sum=0; x--;
	for(;y;y-=lowbit(y)) sum+=c[y];
	for(;x;x-=lowbit(x)) sum-=c[x];
	return sum;
}
int main(){
	scanf("%d%d%d",&n,&ci,&m);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	for(int i=n;i>=1;--i){
		Next[i]=pre[a[i]]; pre[a[i]]=i;
	}
	for(int i=1;i<=ci;++i)
	 if(Next[pre[i]]) add(Next[pre[i]],1);
	for(int i=1;i<=m;++i) {
		scanf("%d%d",&jie[i].l,&jie[i].r); jie[i].id=i;
	}
	sort(jie+1,jie+m+1,comp);
	int le=1;
	for(int i=1;i<=m;++i){
	   while(le<jie[i].l){
			if(Next[le]) add(Next[le],-1);
			if(Next[Next[le]]) add(Next[Next[le]],1);
			le++;
	   }
	   as[jie[i].id]=Que(jie[i].l,jie[i].r);
	}
	for(int i=1;i<=m;++i) printf("%d\n",as[i]);
}
TTTTTTT代码:莫队
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
struct dd{
   int l,r,id;
}jie[1000009];
int belong[1000006],s[1000006],color[1000005],ans,n,m,c;
int as[1000006],bs[1000006];
bool comp(const dd & a,const dd & b){
    if(belong[a.l]==belong[b.l]) return a.r<b.r;
    return a.l<b.l;
}
void update(int x,int rt){
   s[color[x]]+=rt;
   if((s[color[x]]==1&&rt<0)||(s[color[x]]==2&&rt>0))
      ans+=rt;
}
int main(){
    scanf("%d%d%d",&n,&c,&m);
    int bol=(int)sqrt(n+0.5);
    for(int i=1;i<=n;++i) scanf("%d",&color[i]);
    for(int i=1;i<=n;++i) belong[i]=(int)((i-1)/bol)+1;
    for(int i=1;i<=m;++i){
       scanf("%d%d",&jie[i].l,&jie[i].r); jie[i].id=i;
    }
    sort(jie+1,jie+m+1,comp);
    int le=jie[1].l,re=jie[1].r;
    for(int i=le;i<=re;++i) update(i,1);
    if(le==re) as[jie[1].id]=0;
    else as[jie[1].id]=ans;
    for(int i=2;i<=m;++i){
      while(re<jie[i].r) update(++re,1);
      while(re>jie[i].r) update(re--,-1);
      while(le>jie[i].l) update(--le,1);
      while(le<jie[i].l) update(le++,-1);
      if(jie[i].r==jie[i].l) as[jie[i].id]=0;
      else as[jie[i].id]=ans;
    }
    for(int i=1;i<=m;++i) printf("%d\n",as[i]);
    return 0;
}

Noip 2012引水入城:

首先:需要明白一件事,就是在第一行的某个蓄水池的左右边界中间的干旱区域肯定能到达这个蓄水池,不然是无解的;开始先从第一行的每个点dfs,看看是否保证干旱区都被搜到,

如果有不能到达的,无解,输出个数即可;

下面是有解的情况,如果在上边一个点建一个蓄水场,那么它一定是可以覆盖下边一个连续的区域的(一定是连续的,如果是左边和右边都覆盖了,而中间有一个没有覆盖,那么这是无解的情况),一个左区间一个右区间,那么就可以用DP来求解f[i]表示前i个干旱区最少修建蓄水场的个数,f[i]:=min(f[l[j]-1]+1) 前提是蓄水池j可以覆盖到第i格干旱区。那么求出蓄水场的左右边界就是一个问题了,暂且不说从下面每一个干旱区暴搜到上边,那是90分,说下N*M的。

    既然已经知道蓄水场覆盖的是一个连续的区域的话,那么就希望他覆盖的区域最大,那么也就是希望覆盖的左边界越小越好右边界越大越好,既然是N*M的算法,那么也就是说只遍历图一边,从上向下搜,没有办法记录状态,所以还是M遍,考虑从下向上搜索,只不过是从低向高爬(不是流水了,改登山了!),从左边第一个点开始搜,那么标记他走过的路,然后他找到上边的蓄水池,因为他是第一个开始的,所以到达的那些点一定会找到这个最小最左边的点并记录为这个蓄水池所覆盖的左边界(因为左边界越小越好啊),那么接着该从第2个干旱区搜索了,碰到之前搜过的点就不继续搜索了,因为如果继续搜索的,一定回和之前的1号点所走的路是一样的,所到达的蓄水池也不会把第2个点当它的左边界(因为覆盖区间比以前变小了),所以不要走重复的路,把没走过的路走完,然后到达蓄水池,那么这次到达的蓄水池一定是之前没到达过的,记录左边界,这样左边界就找完了。那么右边界可以模仿找左边界,只不过需要从最后一行的n开始搜索,到1号点,倒着来一遍,蓄水池右边界找个最大即可。

    找左右边界理论上是不能用DFS的因为有可能会爆栈,但是实际上是可以的,我是能写DFS不写BFS因为太麻烦,DFS很好写。

这样去年第四题就做完了,写起来太好写了,DNS+BFS+DP,没有各种高级数据结构和高级算法来辅助或直接求解(DP除外,因为他是一种思想),就是不好想啊,如果无法发现一个蓄水场能覆盖下面的一段区间就能至少90分,这是本题的重点。

AC代码:
#include<cstdio>
#include<cstring>
using namespace std;
int f[506],n,m,a[506][506],haiba[506][506],sum,num,op;
int l[506],r[506];
bool vis[506][506],vist[506][506];
int Min(int x,int y){
	return (x>y)?y:x;
}
void dfs(int x,int y){
	if(x<=0||y<=0||x>n||y>m) return;
	vist[x][y]=1;
	if(haiba[x+1][y]<haiba[x][y]&&!vist[x+1][y]) dfs(x+1,y);
	if(haiba[x][y-1]<haiba[x][y]&&!vist[x][y-1]) dfs(x,y-1);
	if(haiba[x-1][y]<haiba[x][y]&&!vist[x-1][y]) dfs(x-1,y);
	if(haiba[x][y+1]<haiba[x][y]&&!vist[x][y+1]) dfs(x,y+1);
}
void Work_left(int x,int y){
	
    if(x<=0||y<=0||x>n||y>m) return;
	vis[x][y]=1;
	if(x==1){
	   l[a[1][y]]=op;
	}
	if(haiba[x+1][y]>haiba[x][y]&&!vis[x+1][y]) Work_left(x+1,y);
	if(haiba[x][y-1]>haiba[x][y]&&!vis[x][y-1]) Work_left(x,y-1);
	if(haiba[x-1][y]>haiba[x][y]&&!vis[x-1][y]) Work_left(x-1,y);
	if(haiba[x][y+1]>haiba[x][y]&&!vis[x][y+1]) Work_left(x,y+1);
}
void Work_right(int x,int y){
    if(x<=0||y<=0||x>n||y>m) return;
	vis[x][y]=1;
	if(x==1){
		r[a[1][y]]=op;
	}
	if(haiba[x+1][y]>haiba[x][y]&&!vis[x+1][y]) Work_right(x+1,y);
	if(haiba[x][y-1]>haiba[x][y]&&!vis[x][y-1])  {Work_right(x,y-1);
		
	}
	if(haiba[x-1][y]>haiba[x][y]&&!vis[x-1][y]) Work_right(x-1,y);
	if(haiba[x][y+1]>haiba[x][y]&&!vis[x][y+1]) Work_right(x,y+1);
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
	 for(int j=1;j<=m;++j)
	   scanf("%d",&haiba[i][j]);
	for(int i=1;i<=m;++i) a[1][i]=++num;
	for(int i=1;i<=m;++i) dfs(1,i);
	for(int i=1;i<=m;++i) if(!vist[n][i]) ++sum;
	if(sum){
		printf("%d\n%d",0,sum); return 0;
	}
	for(int i=1;i<=m;++i){
		op=i;
		if(!vis[n][i]) {
			Work_left(n,i);
		}
	}
	memset(vis,0,sizeof(vis));
	for(int i=m;i>=1;i--){
		op=i;
		if(!vis[n][i]) {
			Work_right(n,i);
		}
	}
	memset(f,25,sizeof(f));
	f[0]=0;
	for(int i=1;i<=m;++i)
	 for(int j=l[i];j<=r[i];++j)
	   f[j]=Min(f[j],f[l[i]-1]+1);
	printf("%d\n%d",1,f[m]);
	getchar();getchar();
	//while(1);
}

Bzoj 密码箱:

题目就是让求<=n的满足其平方是n的倍数+1,首先满足题意的数肯定是大于sqrt(n)的,这个很显然,假设(x+1)(x-1)==k*n,则x是我们要求的一个数,两边同除(x-1),得到如下式子,

X+1=(k*n)/(x-1),设n==S*R,则转化为x+1=(k*S)/(x-1)*R,枚举R的倍数就可以了;

#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
#define LL long long
queue<int>que;
int a[5000];
int n,num;
int main(){
	scanf("%d",&n);
	a[++num]=1;
	for(int i=1;i*i<=n;++i) if(!(n%i)) que.push(n/i);
	while(!que.empty()){
		int op=que.front(); que.pop();
		for(int i=op;i<=n;i+=op){
		   if((LL)(i-1)*(LL)(i-1)%n==1&&i-1<n) a[++num]=i-1;
		   if((LL)(i+1)*(LL)(i+1)%n==1&&i+1<n) a[++num]=i+1;
		}
	}
	sort(a+1,a+num+1);
	for(int i=1;i<=num;++i){
	  printf("%d\n",a[i]);
	  int k=a[i];
	  while(k==a[i]) i++;
	  i--;
	}
}






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值