17.10.24

    • 上午
      • 模拟考试
      • Prob.1(AC)位运算的拆位操作。
      • Prob.2(AC)模型转化,求不成环的边集最大有多少条边,并查集。
      • Prob.3(AC)用到一个小性质 a|b=a+b-a&b
      • 感觉今天题算简单。
    • 下午
      • BOZJ 1067 [SCOI2007]降雨量1mol的特判和分类讨论,改了好久。
        代码:
      #include<cstdio>
      #include<cstring>
      #include<iostream>
      #define MAXN 50005
      #define INF 0x3f3f3f3f
      using namespace std;
      struct info{
      	int year,amount;
      }p[MAXN];
      struct SGT{
      	#define ls lson[u] 
      	#define rs rson[u]
      	int rt,sz,len;
      	int init[MAXN<<1],lson[MAXN<<1],rson[MAXN<<1],maxi[MAXN<<1];
      	void pushup(int u){
      		maxi[u]=max(maxi[ls],maxi[rs]);
      	}
      	void build(int &u,int l,int r){
      		u=++sz;
      		if(l==r){
      			maxi[u]=init[l];
      			return;
      		}
      		int mid=(l+r)>>1;
      		build(ls,l,mid);
      		build(rs,mid+1,r);
      		pushup(u);
      	}
      	int query(int u,int l,int r,int al,int ar){
      		if(al<=l&&r<=ar) return maxi[u];
      		int mid=(l+r)>>1,nmaxi=-INF;
      		if(al<=mid) nmaxi=max(nmaxi,query(ls,l,mid,al,ar));
      		if(mid<ar) nmaxi=max(nmaxi,query(rs,mid+1,r,al,ar));
      		return nmaxi;
      	}
      	#undef ls 
      	#undef rs
      }t;
      int n,m;
      int binary(int x){
      	int l=1,r=n,mid,ans=n+1;
      	while(l<=r){
      		mid=(l+r)>>1;
      		p[mid].year>=x? ans=mid,r=mid-1:l=mid+1;
      	}
      	return ans;
      }
      int main(){
      	freopen("1067.in","r",stdin);
      	freopen("1067.out","w",stdout);
      	scanf("%d",&n);
      	for(int i=1;i<=n;i++)
      		scanf("%d%d",&p[i].year,&p[i].amount),t.init[i]=p[i].amount;
      	t.len=n;
      	t.build(t.rt,1,t.len);
      	scanf("%d",&m);
      	for(int i=1,a,b,l,r;i<=m;i++){
      		scanf("%d%d",&a,&b);
      		if(a>b) {printf("false"); continue;}
      		l=binary(a); r=binary(b);
      		if(p[l].year!=a&&p[r].year!=b) printf("maybe");
      		if(p[l].year==a&&p[r].year!=b){
      			if(l+1<r&&t.query(t.rt,1,t.len,l+1,r-1)>=p[l].amount) printf("false");
      			else printf("maybe");
      		}
      		if(p[l].year!=a&&p[r].year==b){
      			if(l<r&&t.query(t.rt,1,t.len,l,r-1)>=p[r].amount) printf("false");
      			else printf("maybe");
      		}
      		if(p[l].year==a&&p[r].year==b){
      			if(p[r].amount>p[l].amount) printf("false");
      			else {
      				if(l+1<r&&t.query(t.rt,1,t.len,l+1,r-1)>=p[r].amount) printf("false");
      				else{
      					if(r-l+1==b-a+1) printf("true");
      					else printf("maybe");
      				}
      			} 
      		}
      		printf("
      ");
      	}
      	return 0;
      }
      
      • BOZJ 1068 [SCOI2007]压缩

        区间dp。
        dp[l][r][0/1]表示在l前面有一个M,在区间l-r里面有(无)M的最小长度
        (感觉这个状态不好定,是我太弱了吧)
        然后转移分3种:
        1).dp[l][r][0]=min{dp[l][r][0],dp[l][i][0]+r-i}
            表示i~r不压缩
        2).如果区间长度(r-l+1)%2==0且前后两段字符相同(s[l~mid]==s[mid+1~r])
            dp[l][r][0]=min(dp[l][r][0],dp[l][mid][0]+1)
            表示放一个R在mid和mid+1之间
        3).dp[l][r][1]=min{dp[l][r][1],min(dp[l][i][0],dp[l][i][1])+1+min(dp[i+1][r][0],dp[i+1][r][1])}
            表示在i,i+1之间放一个M,那么l~i和i+1~r就是两个独立的区间了。

        代码:

        #include<cstdio>
        #include<cstring>
        #include<iostream>
        using namespace std;
        char s[55]; 
        bool vis[55][55];
        int dp[55][55][2],n;
        bool check(int l,int m){
        	for(int i=0;l+i<m;i++)
        		if(s[l+i]!=s[m+i]) return 0;
        	return 1;
        }
        void dfs(int l,int r){
        	if(vis[l][r]) return;
        	vis[l][r]=1;
        	for(int i=l;i<r;i++) dfs(l,i),dfs(i+1,r);
        	int &ret0=dp[l][r][0],&ret1=dp[l][r][1];
        	ret0=ret1=r-l+1;
        	//1
        	for(int i=l;i<r;i++) 
        		ret0=min(ret0,dp[l][i][0]+r-i);
        	//2
        	if((r-l+1)%2==0){
        		int mid=(l+r)>>1;
        		if(check(l,mid+1)) ret0=min(ret0,dp[l][mid][0]+1);
        	}
        	//3
        	for(int i=l;i<r;i++)
        		ret1=min(ret1,min(dp[l][i][0],dp[l][i][1])+1+min(dp[i+1][r][0],dp[i+1][r][1]));
        }
        int main(){
        	scanf("%s",s+1);
        	n=strlen(s+1);
        	dfs(1,n);
        	printf("%d",min(dp[1][n][0],dp[1][n][1]));
        	return 0;
        }
        
      • BOZJ 1070 [SCOI2007]修车

        神奇费用流。
        把m个员工拆点,分别拆为n个点,
        第i个员工的第j个点用编号idx(i,j)表示,这个点代表i号员工修他的倒数第j辆车。
        如何建边呢?
        1).考虑如果i号员工修的倒数第j辆车(这辆车编号为w)的时间为x,那么这辆车的修理对总时间的贡献为j*x,所以把x号车向idx(i,j)建一条容量为1,费用为j*x的边。
        2).S向每辆车建一条容量为1,费用为0的边。
        3).m个员工拆成的n*m个点向T建一条容量为1,费用为0的边 。
        然后就是最小费用流问题了。

        代码:

        #include<queue>
        #include<cstdio>
        #include<cstring>
        #include<iostream>
        #define INF 0x3f3f3f3f
        using namespace std;
        struct edge{
        	int to,capo,cost,next;
        }e[80000];
        int head[650];
        int n,m,ent=2,S,T;
        void add(int u,int v,int capo,int cost){
        	e[ent]=(edge){v,capo,cost,head[u]}; head[u]=ent++;
        	e[ent]=(edge){u,0,-cost,head[v]}; head[v]=ent++;
        }
        int idx(int i,int j){ //第i个修理工修倒数第j辆 
        	return n+(j-1)*m+i;
        }
        void build(){
        	for(int i=1,x;i<=n;i++)
        		for(int j=1;j<=m;j++){
        			scanf("%d",&x);
        			for(int k=1;k<=n;k++)
        				add(i,idx(j,k),1,k*x);
        		}
        	for(int i=1;i<=n;i++) add(S,i,1,0);
        	for(int i=1;i<=m;i++)
        		for(int j=1;j<=n;j++)
        			add(idx(i,j),T,1,0);
        }
        bool spfa(int &cost){
        	static int dis[650],p[650],low[650];
        	static bool inq[650];
        	queue<int> q;
        	memset(dis,0x3f,sizeof(dis));
        	dis[S]=0; q.push(S); low[S]=INF;
        	while(!q.empty()){
        		int u=q.front(); q.pop(); inq[u]=0;
        		for(int i=head[u];i;i=e[i].next){
        			int v=e[i].to;
        			if(!e[i].capo||dis[v]<=dis[u]+e[i].cost) continue;
        			dis[v]=dis[u]+e[i].cost; 
        			p[v]=i; low[v]=min(low[u],e[i].capo);
        			if(!inq[v]) inq[v]=1,q.push(v);
        		}
        	}
        	if(dis[T]==INF) return 0;
        	cost+=low[T]*dis[T];
        	int u=T;
        	while(u!=S){
        		e[p[u]].capo-=low[T];
        		e[p[u]^1].capo+=low[T];
        		u=e[p[u]^1].to;
        	}
        	return 1;
        }
        void mincost_maxflow(){
        	int cost=0;
        	while(spfa(cost));
        	printf("%.2lf",1.0*cost/n);
        }
        int main(){
        	scanf("%d%d",&m,&n);
        	S=n+n*m+1; T=n+n*m+2;
        	build();
        	mincost_maxflow();
        	return 0;
        }
        
    • 晚上
      • BOZJ 1071 [SCOI2007]组队

        考虑把题目给的式子拆开,并移项:
        A*H + B*V <= A*minH + B*minV + C
        上式表明,对于已知的 minH 和 minV,如果第i如果满足上式,则他可以加入队伍。
         
        把每个人看成一个三元组(H,V,W=A*H + B*V)
        开三个机构体数组,每个数组存下所有人的信息。
        第一个数组(a)用于枚举最小高度
        第二个数组(b)按 V 排序后,用于枚举最小速度
        第三个数组(c)按 W 排序后,用于枚举每个人是否可以加入队伍

        具体的做法:
        枚举第一个数组,固定一个最小高度minH
            枚举第二个数组,固定一个最小的速度minV (此时得到不等式右边 Val=A*minH+B*minV+C)
                然后枚举第三个数据,看对于当前的 minH 和 minV,可以有多少人加入队伍。
                (满足加入队伍的条件是 c[i].w<=Val&&c[i].H>=minH&&c[i].V>=minV)
        显然 N^3,会超时

        尝试优化,利用我们的排序。
        如果对于当前的 minH,枚举到 minV(得到 Val(即不等式右边)),
        我们发现c数组的前k个元素的w值小于等于 Val,
        那么对于下一个枚举的minV'(排序后minV'>minV),得到的Val'必然大于 Val,
        所以c数组的前k个元素同样也小于Val',
        这意味着我们不需要重新从头开始枚举c数组,
        只需要接着上次的k位置一直枚举到这次的前k'个元素(c数组的前k'个元素的w值小于等于 Val')
        所以对于每个枚举到的minH,b数组遍历了一次,c数组也只遍历了一次。
        即复杂度降到 N^2
        另外,还有一个问题,就是前k个元素中选出的满足(minH,minV)这个二元组的人不一定都满足(minH,minV')
        因为之前的满足条件的人在V值上只需保证大于minV,但现在需要保证大于minV',
        怎么处理呢。发现题目中V的范围很小,所以开一个桶,cc[i]表示满足条件的且v值等于i的人数,
        对于一个新的minV'只需把之前的满足条件的人数-cc[minV](minV为上一次枚举到的minV值),
        即减掉前k个元素中在上一个minV满足条件,但在当前的minV'下却不满足条件的人。
        然后就差不多了。

        还有几个细节:(坑了我好久)
        1).枚举的minH和minV可能相矛盾,需要跳过。
            即我们认定minH为最小H,但minV那个人的H更小。
            或我们认定minV为最小V,但minH那个人的V更小
        2).枚举到的minH和minV不满足不等式,需要跳过。
            即对于当前的minH和minV,我们得出一个Val,
            但是minH那个人的w或者minV那个人的w大于Val。
        3).可能会有相同的V值,要及时清空cc数组
            因为存在-cc[minV(上一次枚举的最小速度)]这一操作,但如果有相同的V(即minV==minV')的人的话,
            就会重复减去 cc[minV]这个值。
        (一些细节搞了我2个多小时,真弱啊,)

        代码:

        #include<cstdio>
        #include<cstring>
        #include<iostream>
        #include<algorithm>
        #define ll long long
        using namespace std;
        struct node{
        	ll h,v,w;
        }a[5005],b[5005],c[5005];
        ll cc[10005];
        ll n,p,A,B,C,h,v,mh,mv,val,now,ans;
        bool cmp1(node x,node y){
        	return x.v<y.v;
        }
        bool cmp2(node x,node y){
        	return x.w<y.w;
        }
        int main(){
        	scanf("%lld%lld%lld%lld",&n,&A,&B,&C);
        	for(int i=1;i<=n;i++){
        		scanf("%lld%lld",&h,&v);
        		a[i]=(node){h,v,A*h+B*v};
        		b[i]=c[i]=a[i];
        	}
        	sort(b+1,b+n+1,cmp1);
        	sort(c+1,c+n+1,cmp2);
        	for(int i=1;i<=n;i++){
        		memset(cc,0,sizeof(cc)); 
        		p=1; mh=a[i].h; now=0;
        		for(int j=1;j<=n;j++){
        			now-=cc[b[j-1].v];
        			cc[b[j-1].v]=0;
        			if(a[i].v<b[j].v||b[j].h<a[i].h) continue;
        			mv=b[j].v;
        			val=A*mh+B*mv+C;
        			if(a[i].w>val||b[j].w>val) continue; 
        			while(p<=n&&c[p].w<=val){
        				if(c[p].h>=mh&&c[p].v>=mv) now++,cc[c[p].v]++;
        				p++;
        			}
        			ans=max(ans,now);
        		}
        	}
        	printf("%lld",ans);
        	return 0;
        }
        

原文地址:https://www.cnblogs.com/zj75211/p/7726952.html