• 雅礼学习10.3


    雅礼学习10.3

    各题状况

    T1

    暴力修改+一维差分+二维差分

    莫名其妙就没了49分。。。

    好像是数组开的不够大?

    T2

    这。。。概率和期望,一会不会,连那个一分的部分分都没有任何思路

    T3

    题目并没有看太懂。。

    写了一个枚举算法,然后对某个一分的数据输出显然的结果

    。。。

    然后就只拿了1分

    枚举挂了,因为会错了题目含义

    题目及考场代码

    T1

    图片.png

    图片.png

    /*
     * 一个个修改肯定超时。。
     * q==0的直接输出0
     * 19分应该是暴力
     *
     * 考虑对每次操作,计算一共修改了多少个位置
     * 奇数个的话就让当前答案异或这个数字
     * 对于边界单独讨论
     * 但是问题在于。。。如果修改的位置刚好把当前答案所在位置变大了,就不应该是异或,而是+
     * 真的不会维护啊
     * 假装这么写是正确的吧。。。
     *
     * 每行差分
     * 最后O(n^2)统计
     */
    #include <cstdio>
    
    inline int read()
    {
    	int n=0,w=1;register char c=getchar();
    	while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
    	while(c>='0'&&c<='9')n=n*10+c-'0',c=getchar();
    	return n*w;
    }
    inline int min(int x,int y)
    {return x<y?x:y;}
    
    const int N=1002;
    long long map[N][N],add[N][N],cut[N][N];
    
    int main()
    {
    	freopen("u.in","r",stdin);
    	freopen("u.out","w",stdout);
    	int n=read(),q=read(),r,c,s,l,x;
    	long long ans=0;
    	if(q==0)
    	{
    		printf("0");
    		goto E;
    	}
    	if(q<=400)
    	{
    		while(q--)
    		{
    			r=read(),c=read(),l=read(),s=read();
    			x=min(r+l-1,n);
    			for(int i=r;i<=x;++i)
    				for(int j=c;j<=min(i-r+c,n);++j)
    					map[i][j]+=s;
    		}
    		for(int i=1;i<=n;++i)
    			for(int j=1;j<=n;++j)
    				ans^=map[i][j];
    	}
    	else
    		if(q<=2000)
    		{
    			while(q--)
    			{
    				r=read(),c=read(),l=read(),s=read();
    				x=min(r+l-1,n);
    				for(int i=r;i<=x;++i)
    					map[i][c]+=s,map[i][min(i-r+c,n)+1]+=-s;
    			}
    			for(int i=1;i<=n;++i)
    			{
    				x=0;
    				for(int j=1;j<=n;++j)
    				{
    					x+=map[i][j];
    					printf("%d ",x);
    					ans^=x;
    				}
    				puts("");
    			}
    		}
    		else
    		{
    			while(q--)
    			{
    				r=read(),c=read(),l=read(),s=read();
    				add[r][c]+=s,add[r+l][c]-=s;
    				cut[r][c+1]+=s,cut[r+l][c+l+1]-=s;
    			}
    			for(int i=1;i<=n;++i)
    				for(int j=1;j<=n;++j)
    				{
    					add[i][j]+=add[i-1][j];
    					cut[i][j]+=cut[i-1][j-1];
    				}
    			for(int i=1;i<=n;++i)
    				for(int j=1;j<=n;++j)
    				{
    					map[i][j]+=map[i][j-1]+add[i][j]-cut[i][j];
    					ans^=map[i][j];
    				}
    		}
    			
    	/*
    	else
    	{
    		while(q--)
    		{
    			r=read(),c=read(),l=read(),s=read();
    			x=l+1;
    			if(r+l-1>n || c+l-1>n)
    			{//想了半天没想出来怎么快速把多余的部分删掉。
    				x=0;
    				for(int i=r;i<=min(r+l-1,n);++i)
    					x+=min(i-r+c,n);
    			}
    			if(x&1)ans^=s;
    		}
    	}*/
    	printf("%lld",ans);
    			
    E:	fclose(stdin);fclose(stdout);
    	return 0;
    }
    

    T2

    图片.png

    图片.png

    /*
     * 概率。。。
     * 根本一会不会啊。。。
     * 骗分走人
     */
    #include <cstring>
    #include <cstdio>
    
    const int N=31;
    char s[N];
    
    int main()
    {
    	freopen("v.in","r",stdin);
    	freopen("v.out","w",stdout);
    
    	scanf("%d%d",&n,&k);
    	scanf("%s",s);
    	int x=0;
    	for(int i=0;i<n;++i)
    		if(s[i]=='W')
    			++x;
    	if(k==0 || k==n)
    		printf("%.10lf",(double)1.0*k);
    	else	printf("%.10lf",(double)x*1.0/2);
    
    	fclose(stdin);fclose(stdout);
    	return 0;
    }
    

    T3

    图片.png

    图片.png

    /*
     * 输出0有1分。
     */
    #include <cstdio>
    #include <vector>
    
    int n,step,cnt,k_2,z_2,ku;
    int l_note[100023];
    struct note{
    	int con,have,wash;
    };
    bool dis[100023];
    std::vector<note> tree[100023];
    std::vector<int> kuai[100023];
    void search(int num)
    {
    	for(int space,i=0;i<l_note[num];i++)
    	{
    		space=tree[num][i].con;
    		if(((tree[num][i].wash!=tree[num][i].have)||(tree[num][i].wash==2))&&dis[space]!=true)
    		{
    			tree[num][i].have=tree[num][i].wash;
    			++cnt;
    			dis[num]=true;
    			search(space);
    			break;
    		}
    	}
    }
    bool vis[100023];
    void qk(int num)
    {
    	vis[num]=true;
    	for (int i=0;i<kuai[num].size();++i)
    	{
    		int space=kuai[num][i];
    		if(tree[num][i].wash==2)++k_2;
    		if(vis[space]!=true)
    			qk(space);
    	}
    }
    int main ()
    {
    	freopen("w.in","r",stdin);
    	freopen("w.out","w",stdout);
    	scanf("%d",&n);
    	if(n<=1000)
    	{
    		for(int a,b,c,d,i=1;i<n;++i)
    		{
    			scanf("%d%d%d%d",&a,&b,&c,&d);
    			tree[a].push_back((note){b,c,d});
    			++l_note[a];
    			tree[b].push_back((note){a,c,d});
    			++l_note[b];
    			if((c!=d)||(d==2))
    			{
    				kuai[a].push_back(b);
    				kuai[b].push_back(a);
    			}
    			if(d==2)++z_2;
    		}
    		for(int i=1;i<=n;++i)
    		{
    			for(int o=0;o<l_note[i];o++)
    			{
    				if((tree[i][o].wash!=tree[i][o].have)&&tree[i][o].wash!=2)
    				{
    					dis[tree[i][o].con]=true;
    					search(i);
    					break;
    				}
    			}
    		}
    		for(int i=1;i<=n;++i)
    			if(vis[i]!=true&&kuai[i].size()!=0)
    			{
    				++ku;
    				qk(i);
    			}
    		step=ku-(z_2-k_2/2);
    		printf("%d %d",step,cnt);
    	}
    	else	printf("0 0");
    	fclose(stdin);fclose(stdout);
    	return 0;
    }
    

    正解

    T1

    第一眼(n imes q)做法,(3e8)显然不能过

    那么考虑对其进行优化:差分序列的访问时不连续的,所以我们考虑把它变成连续的

    就过了。。。

    官方正解:

    竖着做一次差分,斜着做一次差分,那么一个三角形就可以确定出来了,而后(n^2)扫一遍求异或和

    #include<bits/stdc++.h>
    using namespace std;
    
    typedef long long ll;
    const int maxn=1e3+10;
    int n,q;
    ll a[maxn][maxn],b[maxn][maxn],ans;
    
    inline void add_a(int x,int y,int v){
    	if(x<=n&&y<=n)
    		a[x][y]+=v;
    }
    inline void add_b(int x,int y,int v){
    	if(x<=n&&y<=n)
    		b[x][y]+=v;
    }
    
    int main(){
    	freopen("u.in","r",stdin);
    	freopen("u.out","w",stdout);
    	scanf("%d%d",&n,&q);
    	while(q--){
    		int r,c,l,s;
    		scanf("%d%d%d%d",&r,&c,&l,&s);
    		add_a(r,c,s);
    		add_a(r+l,c+l,-s);
    		add_b(r+l,c,-s);
    		add_b(r+l,c+l,s);
    	}
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=n;++j){
    			if(i>1)
    				a[i][j]+=a[i-1][j-1]+a[i-1][j]-a[i-2][j-1];
    			else
    				a[i][j]+=a[i-1][j-1]+a[i-1][j];
    			b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1];
    			ans^=a[i][j]+b[i][j];
    		}
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    T2

    可以得到一个(O(2^n imes n))的状压(DP)的做法,记录每个球是否还没有被移除,然后按照最优策略期望移除白球数

    事实上有很多重复状态,也就是剩下的求的颜色序列相同时结果是一样的

    考虑将状态记成剩下的颜色序列,长度较小的时候就直接用数组去存,较大的时候用(map)去存

    状态个数能得到一个上界是(sum_{i=0}^nmin{2^i,{ichoose n}})(事实上最大值为(sum_{i=1}^{n+1}Fib_i)但是这一点也不(noip)

    #include<bits/stdc++.h>
    using namespace std;
    
    const int maxn=30+5;
    int n,k;
    char s[maxn];
    
    namespace ${
    	const int xxx=24;
    	double a[1<<xxx+1];
    	map<int,double> m[maxn];
    	inline void init(){
    		for(int i=0;i<1<<xxx+1;++i)
    			a[i]=-1;
    	}
    	inline bool count(int bit,int len){
    		if(len<=xxx)
    			return a[1<<len|bit]!=-1;
    		else
    			return m[len].count(bit);
    	}
    	inline double&find(int bit,int len){
    		if(len<=xxx)
    			return a[1<<len|bit];
    		else
    			return m[len][bit];
    	}
    }
    inline int erase(int bit,int k){
    	return bit&(1<<k)-1|bit>>1&-1<<k;
    }
    inline double max_(double a,double b){
    	return a>=b?a:b;
    }
    double dfs(int bit,int len){
    	if(len<=k)
    		return 0;
    	if($::count(bit,len))
    		return $::find(bit,len);
    	double&res=$::find(bit,len);
    	res=0;
    	for(int i=0,j=len-1;i<=j;++i,--j)
    		if(i<j)
    			res+=max_(dfs(erase(bit,i),len-1)+(bit>>i&1),dfs(erase(bit,j),len-1)+(bit>>j&1))*2;
    		else
    			res+=dfs(erase(bit,i),len-1)+(bit>>i&1);
    	return res/=len;
    }
    
    int main(){
    	freopen("v.in","r",stdin);
    	freopen("v.out","w",stdout);
    	$::init();
    	scanf("%d%d%s",&n,&k,s);
    	k=n-k;
    	int bit=0;
    	for(int i=0;i<n;++i)
    		bit|=(s[i]=='W')<<i;
    	printf("%.10f
    ",dfs(bit,n));
    	return 0;
    }
    

    T3

    如果最后翻转的边集是(S),最少操作数为({V,S})中奇数度数的点的一半,最小操作总长度是(|S|)

    考虑树形(DP)(dp[i][0/1])记录以(i)为根的子树内,(i)与父亲之间的边是否翻转,最少的奇数度数的点的个数,此时的最小总长度

    #include<bits/stdc++.h>
    using namespace std;
    
    const int maxn=1e5+10;
    const pair<int,int> inf=make_pair(1e9,1e9);
    int n;
    vector<pair<int,int> > g[maxn];
    pair<int,int> dp[maxn][2];
    
    inline pair<int,int> operator+ (pair<int,int> a,pair<int,int> b){
    	return make_pair(a.first+b.first,a.second+b.second);
    }
    void dfs(int pos,int fa,int type){
    	pair<int,int> tmp0(0,0),tmp1(inf);
    	for(int i=0,v;i<g[pos].size();++i)
    		if((v=g[pos][i].first)!=fa){
    			dfs(v,pos,g[pos][i].second);
    			pair<int,int> nxt0,nxt1;
    			nxt0=min(tmp0+dp[v][0],tmp1+dp[v][1]);
    			nxt1=min(tmp1+dp[v][0],tmp0+dp[v][1]);
    			tmp0=nxt0;tmp1=nxt1;
    		}
    	if(type==0||type==2)
    		dp[pos][0]=min(tmp0,make_pair(tmp1.first+1,tmp1.second));
    	else
    		dp[pos][0]=inf;
    	if(type==1||type==2)
    		dp[pos][1]=min(make_pair(tmp0.first+1,tmp0.second+1),make_pair(tmp1.first,tmp1.second+1));
    	else
    		dp[pos][1]=inf;
    }
    
    int main(){
    	freopen("w.in","r",stdin);
    	freopen("w.out","w",stdout);
    	scanf("%d",&n);
    	for(int i=1,a,b,c,d;i<n;++i){
    		scanf("%d%d%d%d",&a,&b,&c,&d);
    		if(d!=2)
    			d=(c!=d);
    		g[a].push_back(make_pair(b,d));
    		g[b].push_back(make_pair(a,d));
    	}
    	dfs(1,0,0);
    	printf("%d %d
    ",dp[1][0].first/2,dp[1][0].second);
    	return 0;
    }
    

    下午讲课:搜索与剪枝

    主要是题目只有题目

    例一

    有一个([1,2^n])的排列(A{1,cdots,2^n})

    可以执行的操作有(n)种,每种操作最多可以执行一次。第(i)种操作:将序列从左到右划分为(2^{n-i+1})段,每段恰好包括(2^{i-1})个数,然后整体交换其中两段。
    求可以将数组(A)从小到大排序的不同的操作序列有多少个。
    两个操作序列不同,当且仅当操作个数不同,或者至少一个操作不同(种类不同或者操作位置不同)。

    (nle 12)

    解:

    首先,任意一个合法的操作序列,我们可以改变其顺序,依然满足条件。
    那么这一类的操作序列的贡献,即为操作次数的阶乘。
    那么,现在只考虑种类编号递增的操作序列。第(i)种操作时,序列分成了大小为(2^{i-1})的段,如果某个段不是递增且连续的,那么最后肯定不会满足条件。所以,在这种操作考虑完后,每个大小为(2^i)的段应当递增且连续。
    当考虑第(i)种操作时:

    • 如果不合条件的(2^i)大小的段超过(2)个,直接退出
    • 如果不存在的话,直接继续
    • 如果只有(1)个,交换其包含的(2^{i-1})的两段,判断是否可行
    • 如果有(2)个就分别搜索(4)种交换方案

    例二

    农夫约翰需要一些特定规格的木材(共(n)块,长度不一定相同),可是他只剩下一些大规格的木板(共(m)块,长度不一定相同)。不过约翰可以将这些木板切割成他所需要的规格。
    求约翰最多能够得到多少他所需要的木材。

    (nle 1000,mle 50,lenthle 32767)

    解:

    显然可以二分答案。
    判断答案为(k)是否可行,显然可以将木材排序,搜索最短的k 块木材是否
    能得到。
    然后剪枝:

    • 从小到大搜索所需的木材从哪块木板得到,因为长的木材限制比较大
    • 如果两块所需木材长度相同,后搜索的那一块的来源只需要从前者的来源开始枚举
    • 当一块原料的长度比最短的需求还短,那么直接丢弃,如果丢弃总量+所需总量(gt)原料总量,剪掉

    例三

    给出一个数字(S),输出所有约数和等于(S)的数

    一共(T)次询问

    (Sle 2e9,Tle 100)

    解:

    (n=prod_i p_i^{a^i}Rightarrow sigma(n)=prod_isum_{j=0}^{a_i}p_i^j)

    那么我们可以通过枚举(p_i)及其(a_i)来搜索

    若当前需要得到的(S)可以表示为为一个未搜索过的质数与(1)的和,那么之前的数与这个质数的乘积是一个合法答案

    对于每个使得((p+1)(p-1)lt S)(p),枚举可能的(a_i)进行递归

    例四

    一个(n imes m)的网格,(k)种颜色,部分格子已经涂了某种颜色,现在需要将其他格子也涂上颜色,使得从((1,1))((n,m))的每条路径(每次向下或向右走一格)都不会出现重复颜色。求方案数,对(10^9+7)取模。

    (n,mle 1000,kle 10)

    解:

    颜色数应大于等于步数,(n+m-1le kRightarrow n+mle k+1le 11),否则puts("0")。
    然后搜每个位置的颜色,可以状压到每个位置的已经过的颜色。
    可行性剪枝:未经过的颜色数小于剩余步数,剪掉。
    对称性剪枝:如果涂上一个未出现过的颜色,涂哪一个都是等价的,那么只需搜其中一个。

    例五

    (52)张牌排成一排({0,1cdots 51}),然后可以把他们分成两半,然后交叉着洗牌(一个确定的置换)。
    洗完牌后,可能会出现一个错误,将一对相邻的牌调换了位置,但是每次洗牌最多只会犯一个错误。
    现在给出牌最终的顺序,求最少洗了多少次(保证不超过(10)),最少犯几次错误。

    解:

    一个性质:确定了洗牌次数后,若真实的最终排列与不犯错误的最终排列,有(k)个位置不同,那么犯错次数(lceilfrac{k}{2} ceil)

    以此来当做剪枝,枚举洗牌次数和犯错次数,跑(idA^*)

    例六

    你记录了([0,59])这个时间段内到站的所有公交车(数量(le 300)) 的时间,每辆车属于一条线路。

    • 同一路线路的车的到站时间间隔相同。
    • 每条线路在([0,59])至少到达两辆车。
    • 最多有(17)条线路

    求最少可能有多少条线路

    解:

    一条线路,可以通过第一辆、第二辆车来确定。
    我们可以按照到站顺序来确定每辆车的归属情况。
    如果它是线路第一辆车,那么先把他做一个标记。(同时,它的时间应在([0,29]))
    如果是第二辆,枚举标记过的车辆来确定一条线路,如果可行,删除该线
    路的所有车。
    这样,随着搜索的深度增加,可选车辆快速减少,搜索规模大量降低。

    同时可以加入若干合法性或者最优性剪枝。比如:作为第一辆的车多于未确定的车。

    例七

    给定(n)个字符集为({a,b,c,cdots,m})的字符串(S_i),求一种({a,b,c,cdots,m})({0,1,2,3,4,5,6,7,8,9,+, imes,=})的映射(f),使得所有(f(S_i))均是表达式合法、且成立的等式。

    (nle 1000,5le |S_i|le 11)

    解:

    先对约束条件较高的(+, imes ,=)进行搜索。
    然后,对于每一个等式,我们得到了每个数的位数,计算等号两侧的值域,
    无交集就可以剪枝。随着对应关系的确定,值域也会越来越小。
    可以选择从低位开始搜,确定了低位的值,不相等可以剪枝

  • 相关阅读:
    Android 剪贴板详解
    Android 7.1
    Windows 上安装 Redis 及可能出现的错误和解决方法!
    微信支付(服务商模式问题集)
    LOG4J日志问题 Failed to load class "org.slf4j.impl.StaticLoggerBinder".
    Mysql强制修改密码
    Mysql密码忘记,修改密码方法
    笔记本最大支持内存查看方法
    Liunx操作指令搜素引擎
    tomcat启动时,内存溢出,Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
  • 原文地址:https://www.cnblogs.com/kuaileyongheng/p/9743527.html
Copyright © 2020-2023  润新知