首页 > 其他分享 >USACO2022Jan,Feb,Open

USACO2022Jan,Feb,Open

时间:2022-11-22 23:14:37浏览次数:46  
标签:... Feb int 枚举 USACO2022Jan ans Open mathfrak las

Drought G

一道难度小于提高 T2 的简单 dp 优化题。

一个显然的性质是:对于一个确定的 \(h\) 数组,如果有解,那么按照“先操作 \((n-1,n)\) 若干次把 \(h_n\) 变成 \(0\)、再操作 \((n-2,n-1)\) 若干次把 \(h_{n-1}\) 变成 \(0\)……”一定最后可以都变成 \(0\)(当然不一定是 \(0\),这里只是举个例子)。

一个最初的想法是枚举公共高度 \(\mathfrak U\),那么把 \(H_1,H_2,...,H_n\) 都减去 \(\mathfrak U\),再进行如下的暴力 DP:
设 \(\mathfrak{F}(i,j)\) 表示前 \(i\) 个数,第 \(i\) 个数已经被 \(i+1\) 操作了 \(j\) 次的情况下,\(i\) 元组 \((h_1,h_2,...,h_i)\) 有几种合法方案。转移显然:\(\mathfrak{F}(i,j)=\sum_{k=j}^{H_i}\mathfrak{F}(i-1,k-j)\)。边界是 \(\mathfrak F(1,0)=\mathfrak F(1,1)=...=\mathfrak F(1,H_1)=1\)。复杂度 \(O(nH^2)\)。

直觉告诉我们这个公共高度的枚举十分多余,转而我们就发现对于 \(n\) 为偶数的情况,每一种有解情况我们钦定都变成 \(0\) 亦有解,因为当你把它都变成同一高度 \(\mathfrak U\) 时,将 \((1,2),(3,4),...,(n-1,n)\) 再各操作 \(\mathfrak U\) 次就都变成 \(0\) 了。这样一来,对于 \(n\) 为偶数的情况,直接做上面的 DP 并取 \(\mathfrak F(n,0)\) 即为答案。

对于奇数的情况我们似乎感到没有进一步优化空间,转而看到转移可以表示为 \(\mathfrak{F}(i,j)=\sum_{k=0}^{H_i-j}\mathfrak{F}(i-1,k)\),我们便自然地想到前缀和优化,也就是说 DP 可以做到 \(O(nH)\)。这样一来我们完全可以枚举 \(\mathfrak U\) 了。

#include <bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int n,mnh=mod,H[105],f[105][1005];
inline void add(int &x,int y){(x+=y)>=mod&&(x-=mod);}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&H[i]),mnh=min(mnh,H[i]);
	for(int i=0;i<=H[1];i++)f[1][i]=1;for(int i=1;i<=1000;i++)add(f[1][i],f[1][i-1]);
	for(int i=2;i<=n;i++){
		for(int j=0;j<=H[i];j++)f[i][j]=f[i-1][H[i]-j];
		for(int j=1;j<=1000;j++)add(f[i][j],f[i][j-1]);
	}
	if(n&1){
		int sum=0;
		for(int i=0;i<=mnh;i++){
			memset(f,0,sizeof f);
			for(int i=0;i<=H[1];i++)f[1][i]=1;for(int i=1;i<=1000;i++)add(f[1][i],f[1][i-1]);
			for(int i=2;i<=n;i++){
				for(int j=0;j<=H[i];j++)f[i][j]=f[i-1][H[i]-j];
				for(int j=1;j<=1000;j++)add(f[i][j],f[i][j-1]);
			}
			add(sum,f[n][0]);
			for(int j=1;j<=n;j++)H[j]--;
		}
		cout<<sum;
	}
	else cout<<f[n][0];
}

Redistributing Gifts G

最暴力的:设 \(f(s,t)\) 表示奶牛集合为 \(s\),礼物集合为 \(t\),选法方案数。每次枚举 \(\text{lowbit}\) 的奶牛配对转移,\(O(4^n)\)。

发现反正是顺次给奶牛分配礼物,所以不必大费周章用 bitmask 记录奶牛状态;并且考虑对于所有 \(2^n\) 个子集 \(\mathfrak S\) 整个算一次 \(ans_{\mathfrak S}\) 表示 G 型牛的位置状态为 \(\mathfrak S\) 时的答案(两种牛没有本质不同所以最后 \(ans_{\mathfrak S_{\tt G}}\cdot ans_{\mathfrak S_{\tt H}}\) 就是答案):设 \(f(i,t)\) 表示考虑了 \(p_1,p_2,...,p_i\) 的牛,可供选择的礼物集合为 \(t\) 的方案数;其中 \(p_1,...,p_{|\mathfrak S|}\) 表示该询问中为 G 的位置的编号。每次考虑给 \(p_i\) 分配的礼物转移。单次询问复杂度 \(O(|\mathfrak S|2^{|\mathfrak S|})\)。
对于这一部分的总复杂度分析:

\[O\left(\sum_{\mathfrak S}|\mathfrak S|2^{|\mathfrak S|}\right)\le O\left( n\sum_{\mathfrak S} 2^{|\mathfrak S|}\right)=O(n3^n) \]

考虑最开始的暴力的做法,显然,礼物集合的大小等于奶牛集合的大小才有意义,然后这种情况下,奶牛和礼物是配对出现的,对于关联性如此之强的两个集合,我们却用了二次方来存,直觉告诉我们这是冗余的。

考虑将奶牛和礼物之间的关联具体化,可通过建立有向边 \(\mathrm {cow\to gift}\) 的方式。\(n\) 点 \(n\) 边,这是一个由若干个有向环构成的图,则可以次第加点,考虑是加入上次环的开口还是闭上上次环并新开一个环起点接口。

设 \(f(s,las)\) 表示所加点集为 \(s\),最后加的点为 \(las\),有标号图形态的方案数。枚举下一个要加的点进行转移,会发现这样子,一方面,每个环都可以任意一点作为起点了,从而有多重表示方法,产生错误,另一方面,不同环之间的顺序随意了,也产生错误。这是一个让我们去重的问题,考虑钦定,钦定每个环的起点必须是环中最大点,钦定按照环中最大点递增的顺序依次考虑各个环。
这样,每次枚举下一个要加的点 \(j\),并记当前 \(s\) 的 \(\text{highbit}\) 为 \(v\)(也即当前正在考虑的环的最大点):

  • \(j<v\),表示添加 \(las\to j\) 的边,转移到 \(f(s\oplus 2^j,j)\)
  • \(j=v\),表示添加 \(las\to v\) 的边,将环关闭,不做转移,而将 \(ans_s\gets ans_s+f(s,las)\)
  • \(j>v\),表示添加 \(las\to v\) 的边,将环关闭,并新开一环,转移到 \(f(s\oplus 2^j,j)\)

当然了,转移前还要判断所加的边是否满足题目中的配对要求。

至此,我们用 \(O(n^22^n+nq)\) 的复杂度解决了问题。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,q,rk[20][20],aa[1<<18];
ll f[1<<18][20],ans[1<<18];
char str[20];
inline int ppc(int x){int cnt=0;while(x)cnt++,x-=x&-x;return cnt;}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		for(int j=1,tmp;j<=n;j++){
			scanf("%d",&tmp);
			rk[i-1][tmp-1]=j;
		}
	}
	for(int i=1,j=0;i<(1<<n);i<<=1,j++)aa[i]=j;
	ans[0]=1;
	for(int s=1;s<(1<<n);s++){
		if(ppc(s)==1)f[s][aa[s]]=ans[s]=1;
		int v;
		for(int j=n-1;~j;j--)if(s>>j&1){v=j;break;}
		for(int las=0;las<n;las++)if(s>>las&1){
			for(int j=0;j<v;j++)if(!(s>>j&1)&&rk[las][j]<=rk[las][las]){
				f[s^(1<<j)][j]+=f[s][las];
			}
			if(rk[las][v]<=rk[las][las]&&ppc(s)>1)ans[s]+=f[s][las];
			for(int j=v+1;j<n;j++)if(!(s>>j&1)&&rk[las][v]<=rk[las][las]){
				f[s^(1<<j)][j]+=f[s][las];
			}
		}
	}
	scanf("%d",&q);
	while(q--){
		scanf("%s",str);
		int st1=0,st2=0;
		for(int i=0;i<n;i++)if(str[i]=='H')st1|=1<<i;else st2|=1<<i;
		printf("%lld\n",ans[st1]*ans[st2]);
	}
}

标签:...,Feb,int,枚举,USACO2022Jan,ans,Open,mathfrak,las
From: https://www.cnblogs.com/impyl/p/16916830.html

相关文章

  • error:could not open ...jvm.cfg解决方法
     出现这种情况大多是因为电脑上之前安装过JDK,卸载重装之后,运行java命令会出现error:couldnotopen…jvm.cfg的错误。打开系统环境变量,查看PATH,会看到诸如此类的配置......
  • Anaconda的 tensorflow(cpu) 与OpenCV安装教程
    安装Anaconda5.2+tensorflow1.9下载Anaconda5.2.0(64位或32位)https://www.anaconda.com/download/安装Anaconda5.2.0(一路确定即可)打开Anacondaprompt,然后执行piplist......
  • OpenCV-Python之ROI和泛洪填充
    1.ROI感兴趣区域的操作寻找感兴趣的区域主要就是利用矩阵的切片功能来提取.如face=image[100:200,300:400]importcv2ascvimage=cv.imread('./data/lena.jpg',......
  • Matplotlib数据可视化——显示图片【对比OpenCV显示图片】
    第一个是自己建立了一个矩阵当做图片显示,代码和图片如下:A=[0.3136,0.3654,0.4237, 0.3653,0.4396,0.5251, 0.4237,0.5251,0.6515]image=np.array(A).resh......
  • OpenCV-Python之图像阈值化
    OpenCV-Python之图像阈值化这篇笔记主要介绍全局阈值和局部阈值两方面。关于阈值化方法OTSU:内方差最小,外方差最大Triangle:直方图为三个波峰或者生物中的细胞图像最为......
  • OpenCV找圆
    #include"opencv2/imgproc/imgproc.hpp"#include"opencv2/highgui/highgui.hpp"#include<stdlib.h>#include<stdio.h>#include<iostream>usingnamespacecv;usingnam......
  • opencv实现人脸识别和眼部识别
    代码importcv2ascvimg=cv.imread("./lena.jpg")gray=cv.cvtColor(img,cv.COLOR_BGR2GRAY)face_cascade=cv.CascadeClassifier('/usr/local/share/opencv4/haarcas......
  • 利用opencv拼接图像视频摄像头进行录像
    将图像拼接成视频格式今天想将5000张图片转换成视频格式,操作如下:importosimportcv2importnumpyasnppath='/home/violet/PycharmProjects/deepSort/images/img1/'fil......
  • vue+ openlayers + GeoServer 地图初始化 标点加弹窗看详情
    <template><divclass="mapCont"><divid="map"><divid="popup"ref="popup"class="ol-popup"v-show="vehiclePointInfo"><divid="popup-......
  • OpenHarmony 3.2 Beta多媒体系列——音视频播放框架
    一、简介媒体子系统为开发者提供一套接口,方便开发者使用系统的媒体资源,主要包含音视频开发、相机开发、流媒体开发等模块。每个模块都提供给上层应用对应的接口,本文会对音视......