恩欧挨批模拟试题-19

水博客太快乐了

RT

考场

看到题目异常眼熟。。。

\(u,v,w\)

几位大佬以前做过。。。。
理论上来说我也做过但是没什么映像。。。。

先看 \(T1\) 这不是个简单的二维前缀和??
感觉貌似不能二维前缀和。。。尝试三角形的二维前缀和。。。
貌似也不对。。。先看下一道。。。

\(T2\) 期望 \(dp\) ,显然不会,写了暴力骗分走人。。。

再看 \(T3\) 是一个树形 \(dp\) 设计了一个感觉比较正确的状态,但是不太会转移,于是胡了挺长时间的转移,感觉貌似能过,但是也没时间写对拍了。。。。

分数

挂惨了。。。

\(t1\) \(2pts\) \(+\) \(t2\) \(20pts\) \(+\) \(t3\) \(14pts\) \(=\) \(54pts\)

越考越菜了。。。。
发现 \(t1\) 就是一个简单的二维前缀和。。。
\(t3\) 转移的时候挂了,变成 \(14pts\) 。。。。

题解

A. u

就是简单的二维前缀和。。。。
但是维护等腰直角三角形的前缀和确实第一次见。。。。

将一个三角形扩张成一个从左上角顶点开始,一直到矩阵最下方结束的大三角,将其拆成两个小三角形和一个正方形,正方形显然可以用二维前缀和,三角形也可以。。。
求和时要注意 \(sum_{i,j}=sum_{i-1,j}+sum_{i-1,j-1}-sum_{i-2,j-1}\)
记录在代码里。。。。。

code
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
inline int read(){
	int f=1, x=0; char ch=getchar();
	while(!isdigit(ch)) { if(ch=='-') f-=1; ch=getchar(); }
	while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
	return f*x;
}
int n, q;
long long a[N][N], b[N][N], ans;
int main(void){
	n=read(), q=read();
	int x, y, l, s;
	while(q--){
		x=read(), y=read(), l=read(), s=read();
		a[x][y]+=s; a[min(x+l, n+1)][min(y+l, n+1)]-=s;
		b[min(x+l, n+1)][y]+=s; b[min(x+l, n+1)][min(y+l, n+1)]-=s;
	}
	for(int i=1; i<=n; ++i){
		for(int j=1; j<=n; ++j){
			a[i][j]+=a[i-1][j]+a[i-1][j-1]; 
			if(i>2) a[i][j]-=a[i-2][j-1];
			b[i][j]+=b[i-1][j]-b[i-1][j-1]+b[i][j-1];
		}
	}
	for(int i=1; i<=n; ++i) for(int j=1; j<=n; ++j) ans^=a[i][j]-=b[i][j];
	printf("%lld\n", ans);
	return 0;
}

B. v

考虑状压。。。
\(1\) 表示当前位置为白,用 \(0\) 表示当前状态为黑。。。
但是这样会有 \(bug\)\(001\)\(0001\) 虽然是两种不同状态,转换成一个数后却是相同的,所以考虑在最高位 \(+1\) 变成 \(1001\)\(10001\) ,感觉会变成将来一个很常见的套路,最好记录一下。。
乍一看数据范围貌似不能状压,但是其中显然会有很多状态没有用到,所以可以用 \(hash\) 表或 \(map\) ,有些状态可能会多次出现,因此可以记忆化搜索。。。。

由于每次拿出一个球可以从两个不同的位置拿,且要求最有策略,所以每次把抽两个位置的都算出来,取 \(max\) 即可。。。

code
#include<bits/stdc++.h>
using namespace std;
#define int unsigned int 
const int N=40, mod=30000019, M=40000000, NM=6000000;
int n, m;
char ul[N];
struct hash_map{
	int head[M], cnt=0;
	struct node{
		int nxt, t;
		double w;
	}l[NM];
	double &operator[](int st){
		int u=st%mod;
		for(int i=head[u]; i; i=l[i].nxt)
			if(l[i].t==st) return l[i].w;
		l[++cnt].t=st; l[cnt].w=-1; l[cnt].nxt=head[u];
		head[u]=cnt; return l[cnt].w;
	}
}mp;
double dfs(int k, int con){
	if(k==n-m) return 0;
	if(mp[con]!=-1) return mp[con];
	mp[con]=0;
	double sum=0;
	for(int i=1; i<=(k>>1); ++i){
		int ul1=(con>>(i-1))&1, ul2=(con>>(k-i))&1;
		int t1=((con>>i)<<(i-1))|(con&((1<<(i-1))-1));
		int t2=((con>>(k-i+1))<<(k-i))|(con&((1<<(k-i))-1));
		sum+=2*max(dfs(k-1, t1)+ul1, dfs(k-1, t2)+ul2)/k;
	}
	if(k&1){
		int i=(k+1)>>1;
		int ul1=(con>>(i-1))&1, t1=((con>>i)<<(i-1))|(con&((1<<(i-1))-1));
		sum+=(dfs(k-1, t1)+ul1)/k;
	}
	return mp[con]=sum;
}
signed main(void){
	scanf("%d%d", &n, &m);
	scanf("%s", ul+1);
	int con=0;
	for(int i=1; i<=n; ++i)
		if(ul[i]=='W') con|=(1<<(i-1));
	con|=(1<<n);
	printf("%.10lf\n", dfs(n, con));
	return 0;
}

C. w

树形 \(dp\)
考虑状态,这个状态很好想。。。

因为要翻转的是边,考虑将边下传到点上。。。
则每个点的状态分别表示 \(f[u][0/1]\) 分别表示它连接它父亲的边是否翻转时,最小路径数与最小路径长度。。。
由于当两条路径同时连到一个点时,这两条路径显然可以合并成一条路径,而在计算时是直接加,因此在输出最终答案时需要将最小路径数除二。。

转移很有趣,设过程量 \(p, q\) 分别表示有偶数个点像当前点连边或奇数点向当前点连边。
每次更新时,令 :
\(p=min(p+f[v][0], q+f[v][1])\)
\(q=min(p+f[v][1], q+f[v][0])\)
最终再根据其父亲节点与当前节点的边的状态更新 \(f\) 数组即可。

这在将来貌似也会成为一个常见的套路,最好记录一下。。。。

code
#include<bits/stdc++.h>
using namespace std;
#define f first 
#define s second
const int N=1e5+10, INF=0x3f3f3f3f;
inline int read(){
	int f=1, x=0; char ch=getchar();
	while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
	while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
	return f*x;
}
int n;
struct edge{
	int t, c;
};
vector<edge> l[N];
pair<int, int > f[N][2];
pair<int, int > operator + (const pair<int, int > &x, const pair<int, int > &y) { return make_pair(x.f+y.f, x.s+y.s); }
void dfs(int u, int fa, int op){
	pair<int, int > p=make_pair(0, 0), q=make_pair(INF, INF), a, b;
	for(edge v : l[u]){
		if(v.t==fa) continue;
		dfs(v.t, u, v.c);
		a=min(p+f[v.t][0], q+f[v.t][1]);
		b=min(p+f[v.t][1], q+f[v.t][0]);
		p=a, q=b;
	}
	if(op==0) f[u][1]=make_pair(INF, INF), f[u][0]=min(p, q+make_pair(1, 0));
	else if(op==1) f[u][1]=min(p+make_pair(1, 1), q+make_pair(0, 1)), f[u][0]=make_pair(INF, INF);
	else f[u][1]=min(p+make_pair(1, 1), q+make_pair(0, 1)), f[u][0]=min(p, q+make_pair(1, 0));
}
signed main(void){
	n=read();
	int x, y, c, d;
	for(int i=1; i<n; ++i){
		x=read(), y=read(), c=read(), d=read();
		c=c==d ? 0 : d==2 ? 2 : 1;
		l[x].push_back((edge){y, c});
		l[y].push_back((edge){x, c});
	}
	dfs(1, 0, 0);
	printf("%d %d\n", f[1][0].f>>1, f[1][0].s);
	return 0;
}
posted @ 2021-07-19 09:58  Cyber_Tree  阅读(40)  评论(0编辑  收藏  举报