noip 模拟 6

果然考试一多就改不过来了

考试经过

上来看题,T1似乎是一个计数题,但看见1e9的数据范围就觉得不可做,拿了20部分分匆忙跑路
T2是个图论题,不过一看统计种类就发现是自己不会的东西,瞄准30分冲了一发dfs,再次跑路
T3概率期望???看了几遍题没看明白,果断跳过……
(现在不到两个小时,我已经无法做出T1-3了)
T4一看,蓝书原题???好像确实是,不过似乎是状压。。。突然想起来蓝书上好像说能dfs,于是开码,基本上码了2h,各种搜索一应俱全,觉得可能会AC,然后剩下时间回去看题,依然无法得到正解。。。看见T3好像有个说n=1,这不是可以特判吗?写上去了提交了
还有两分钟结束,看见好像有个k>n,认为这个也可以水分,就加了个特判,一编译突然CE了......以极限手速在结束前一秒交上了加上分号后的代码,虚惊一场
结果:20+0+5+50=75 rank4
前三名ICEY,战神,nanfeng,都好几百分,碾一大截emmmm,发现自己只会打暴力,根本想不出正解
T1是个暴力题,冲上去就行;T230全挂了,难受;T3没辜负我给了5分,T4我又TLE了,嘎

T1.辣鸡

不用被那个1e9吓到,只要分别枚举两个块就行,n2暴力减个枝就能过
答案来源于两部分:块内的和块之间的;块内的可以直接算出来,柿子也很好推,谁TM知道光写个这就有65啊
考虑块之间的,直接拿矩阵存当然会爆,战神提醒我们:

对每两个块,可以O1算出他们之间的氢键数量

那么就好说了,对每个块按x1从小到大排序,一个暴力是n2枚举两个块计算,这里有一个剪枝:\(i_x2+1<j_x1\)时,直接break,因为这两个矩形已经没有连边,剩下的更不可能,没有必要枚举,加上就AC,水的很
注意这里按x排序,不要按y,因为有几个数据y<=2,纵向密度极大,基本减不掉枝,最好只枚举一遍,算的时候分情况,if要判断好

#include <bits/stdc++.h>
using namespace std;
#define int long long
struct H2O{
	int x1,y1,x2,y2;
}a[100005];
bool cmp1(H2O x,H2O y)
{
	return x.x1<y.x1;
}
inline int get(H2O x,H2O y)
{
	if(x.x2+1<y.x1)return 0;
    if(y.x1==x.x2+1)
    {
		if(x.y2<y.y1-1||x.y1>y.y2+1)return 0;
		if(x.y2==y.y1-1||x.y1==y.y2+1)return 1;
    	if(x.y1==y.y1&&x.y2==y.y2)return 2*(x.y2-x.y1);
    	if(x.y1==y.y1)return 2*(min(x.y2,y.y2)-x.y1)+1;
    	if(x.y2==y.y2)return 2*(x.y2-max(x.y1,y.y1))+1;
	 	return 2*(min(x.y2,y.y2)-max(x.y1,y.y1))+2;
	 	
    }
    if(y.y1==x.y2+1||x.y1==y.y2+1)
    {
		if(x.x2<y.x1-1)return 0;
		if(x.x2==y.x1-1)return 1;
    	if(x.x1==y.x1&&x.x2==y.x2)return 2*(x.x2-x.x1);
    	if(x.x1==y.x1)return 2*(min(x.x2,y.x2)-x.x1)+1;
    	if(x.x2==y.x2)return 2*(x.x2-max(x.x1,y.x1))+1;
	 	return 2*(min(x.x2,y.x2)-max(x.x1,y.x1))+2;
	}
	return 0;
}
signed main()
{
	int ans=0;
	int n;cin>>n;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld%lld%lld%lld%",&a[i].x1,&a[i].y1,&a[i].x2,&a[i].y2);
		ans+=2*(a[i].x2-a[i].x1)*(a[i].y2-a[i].y1);
	}
	sort(a+1,a+n+1,cmp1);
	for(int i=1;i<=n;i++)
	 for(int j=i+1;j<=n;j++)
	 {
	 	if(a[i].x2+1<a[j].x1)break;
	 	ans+=get(a[i],a[j]);
	 }
	cout<<ans;
	return 0;
}  

其实还可以引入第二关键字排序,并且在横纵两个方向剪枝,战神的最优似乎可到nlogn,见此

T2.模板

30分写挂了因为没有考虑相同颜色的球没有贡献但占空间,正解是线段树启发式合并,没学呢,不得不咕啊

T3.大佬

其实这个题是个假期望
首先要读懂题(不太容易),这个题仔细分析一下会发现,要求每一段每个难度值成为最大的概率
题中说每个题的难度随机,那么k段就都是一样的,只用求出一段再乘k就行
一般这类题有两种打法:期望打法和概率打法,期望打法是dp求出每种状态的状态数最后除以总数,概率打法是按照概率推
我觉得概率更好打一些,先推一边柿子:
$ f_1=(\frac{1}{n})^k $
$ f_2=(\frac{2}{n})^k-f_1 $
......
$ f_m=(\frac{m}{m})^k -\sum_{i=1}^{m-1}f{i} $
如果最大是1的话,这m天就必须全是1才行,于是有柿子1
如果最大是2,就必须只有1和2,但还要减去全是1的情况,得出2式
下面应该就不难想了,一直推下去到m,就是1减去前面的总和
还不够明显吗,前缀和求一遍,递推On解决,其实是水题
关于分数取模,就是分子乘分母的逆元再取模,知道这个就能理解样例了

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
inline int ksm(int a,int b,int p)
{
	int ans=1%p;
	a=a%p;
	for(;b;b>>=1)
	{
		if(b&1)ans=ans*a%p;
		a=a*a%p; 
	}
	return ans;
}
inline int ga(int x,int y)
{
	return x%mod*ksm(y,mod-2,mod)%mod; 
}
int n,m,k;
int mt[505],f[505],s[505];
signed main()
{
	cin>>n>>m>>k;int sum=0;
        for(int i=1;i<=m;i++)scanf("%lld",&mt[i]);
        if(k>n)
	{
		cout<<0;
		return 0; 
	 }
	for(int i=1;i<=m;i++)
	{
	  f[i]=(ga(ksm(i,k,mod),ksm(m,k,mod))-s[i-1]+mod)%mod;
	  s[i]=(s[i-1]+f[i])%mod;
	}
	int ans=0;
	for(int i=1;i<=m;i++)ans=(ans+f[i]*mt[i]%mod)%mod;
	ans=ans*(n-k+1)%mod;
	cout<<ans;	
	return 0;
}

T4.宝藏

蓝书上的状压例题,对就是例题,然鹅考场上只有ICEY满了,%%%付队
首先在不想正解的情况下可以爆搜,保底50,如果你剪枝完全可以A掉,挺玄学的,洛谷上最快0ms就离谱,爆碾标算
还有更无脑的rp test模拟退火,有意咨询TLEer。。。
蓝书上提供了两种状压做法,都是进行分层转移,主要是处理到根节点距离这个问题,第一种预处理一大堆东西然后转移,第二种用三进制表示状态都不太会
个人比较推付队做法,我们在每次转移时,用数组记录下这种状态每个点到根节点的距离,相当于是dp状态的附属物,转移的时候一起更新
关于后效性的问题,由于转移过程不唯一,所以可以覆盖所有状态,故无后效性,证明见ICEY博客
应该能看出来有不少重边,所以一开始要判重,最好用邻接矩阵

#include <bits/stdc++.h>
using namespace std;
#define int long long
int n,m;int d[15][15];
int f[1<<12];int ga[15][1<<12];
vector <int> sta[13];
inline int getsum(int x)
{
	int an=0;
	while(x)
	{
		if(x&1)an++;
		x=x>>1;
	}
	return an;
}
signed main()
{
	cin>>n>>m;
	memset(d,0x3f,sizeof(d));
	for(int i=1;i<=m;i++)
	{
		int x,y,z;scanf("%lld%lld%lld",&x,&y,&z);
		d[x][y]=min(d[x][y],z);d[y][x]=min(d[y][x],z);
	}
	int ans=999999999;
	for(int i=0;i<(1<<n);i++)
        sta[getsum(i)].push_back(i);
	for(int x=1;x<=n;x++)
	{
	     memset(f,50,sizeof(f));
	     memset(ga,50,sizeof(ga));
	     f[1<<(x-1)]=0;ga[x][1<<(x-1)]=1;
	     for(int i=1;i<=n;i++)
	     for(int j=0;j<sta[i].size();j++)
	     {
	     	int s=sta[i][j];
	     	if(f[s]>1e8)continue;
	     	for(int l=1;l<=n;l++)
	     	{
	     		if(!((s>>(l-1))&1))continue;
				for(int k=1;k<=n;k++)
				{
					if((s>>(k-1))&1)continue;
					if(d[l][k]>1e8)continue;
					int ss=s|(1<<(k-1));
					if(f[s]+ga[l][s]*d[l][k]<f[ss])
					{
					    for(int p=1;p<=n;p++)ga[p][ss]=ga[p][s];
					    ga[k][ss]=ga[l][s]+1;
					    f[ss]=f[s]+ga[l][s]*d[l][k];
					}
				}
			}
	    }
	    ans=min(ans,f[(1<<n)-1]);
	}   
	cout<<ans;
	return 0;
}  

一定注意只有成功转移时临时数组才能更新,我调了一天就因为这。。。

考试总结

1.一定要仔细读题
2.平常多看书
3.不要被某些看着很唬的题目吓住,有时候其实并不难

posted @ 2021-06-11 17:13  D'A'T  阅读(56)  评论(0)    收藏  举报