2021.10.1&2 国庆集训测试

考得都不咋地,2号的考试甚至创下了新的记录,28分!好家伙,要不是USACO有友情分(测试点1永远是样例),我就爆零了。实在悲惨。说实话,突然感觉提高组非常虚。

不过说实话,这六道题都不算太难,至少大多数题代码打起来都很简单(2KB不到吧),也没有太***钻的思路;现在反思看来,每道题我都是想到正解,至少是往正解的方向想了的,但就是没把思路贯通,永远梗着。不过每道题在听完点拨之后,我都可以在半个小时(甚至二十分钟)以内通过题目。综上,我自认为代码能力问题不大,主要就是练题太少,以后得加把劲,多练题,以及多总结。

好吧,上题解。

Day 1 动态规划专题

T1 snakes

暴力即可,当时是打了个\(O(N^3)\)的暴力DP,考场上经过极其不严谨的计算认为会超时,就一直在想如何优化(比如用线段树优化到\(O(N^2logN)\)),于是就花费了许多时间,最后评测,惊喜意外莫名满分。

没什么好说的。

考虑dp[i][j]表示前i组蛇用j次切换机会的最少浪费,枚举转移点,更新。

#include<cstdio>
#include<cstring>
//#define zczc
using namespace std;
const int N=610;
inline void read(int &wh){
    wh=0;int f=1;char w=getchar();
    while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
    while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar(); }
    wh*=f;return;
}
inline int min(int s1,int s2){
	return s1<s2?s1:s2;
}
inline int max(int s1,int s2){
	return s1<s2?s2:s1;
}

int m,n,a[N],sum[N],dp[N][N];

namespace st{
	int b[N][10],log[N];
	void init(){
		for(int i=1;i<=m;i++){
			b[i][0]=a[i];
		}
		for(int i=1;i<10;i++){
			for(int j=1;j+(1<<i)-1<=m;j++){
				b[j][i]=max(b[j][i-1],b[j+(1<<i-1)][i-1]);
			}
		}
		log[0]=-1;
		for(int i=1;i<=m;i++){
			log[i]=log[i>>1]+1;
		}
		return;
	}
	inline int work(int l,int r){
		int size=log[r-l+1];
		return max(b[l][size],b[r-(1<<size)+1][size]);
	}
}

inline int cost(int l,int r){
	return st::work(l,r)*(r-l+1)-sum[r]+sum[l-1];
}

signed main(){
	
	#ifdef zczc
	freopen("in.txt","r",stdin);
	#endif
	
	read(m);read(n);
	for(int i=1;i<=m;i++){
		read(a[i]);
		sum[i]=sum[i-1]+a[i];
	}
	st::init();
	memset(dp,0x3f,sizeof(dp));
	int maxn=0;
	dp[0][0]=0;
	for(int i=1;i<=m;i++){
		dp[i][0]=cost(1,i);
		for(int j=min(i,n);j;j--){
			for(int k=i;k;k--){
				dp[i][j]=min(dp[i][j],dp[k-1][j-1]+cost(k,i));
			}
		}
	}
	int ans=1e9;
	for(int i=0;i<=n;i++)ans=min(ans,dp[m][i]);
	printf("%d\n",ans);
	
	return 0;
}

T2 Exercise

听完讲解之后,感觉好神奇,这可是正常人能想出来的方法。

首先把问题转化成:

假如有一个正整数集合A,满足\(\sum{A_i}\le m\),那么集合内所有元素的gcd,假设为b,就称b是符合条件的数,求所有b的和,取模。

想到这一步很容易,接下来就很玄乎了。它是说把这个东西转化成背包问题,每个数相当于是一个物品,向后依次更新即可。

然后就有个问题是这样更新出来的值是错误的,因为相同的gcd可能会由不同的集合A更新,这会导致结果的不准确。想到用欧式筛的同样思路,如何用一种方式,唯一地表示它(或者说找到它的唯一构成方法)呢。于是想到唯一分解定理。把原先正整数物品替换成质数物品,把原来的01背包变成完全背包,即可。

代码超短。

#include<cstdio>
//#define zczc
#define int long long
const int N=10010;
const int M=3000;
inline void read(int &wh){
    wh=0;int f=1;char w=getchar();
    while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
    while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar(); }
    wh*=f;return;
}

int m,mod,cnt,p[M],dp[M][N];
bool is[N];

signed main(){
	
	#ifdef zczc
	freopen("in.txt","r",stdin);
	#endif
	
	read(m);read(mod);
	for(int i=2;i<=m;i++){
		if(is[i])continue;
		p[++cnt]=i;
		for(int j=i;j<=m;j+=i)is[j]=true;
	}
	dp[0][0]=1;
	for(int i=1;i<=cnt;i++){
		for(int j=0;j<=m;j++){
			dp[i][j]+=dp[i-1][j];
			for(int k=p[i];k<=j;k*=p[i]){
				dp[i][j]+=dp[i-1][j-k]*k;
				dp[i][j]%=mod;
			}
			dp[i][j]%=mod;
		}
	}
	int ans=0;
	for(int i=0;i<=m;i++){
		ans+=dp[cnt][i];ans%=mod;
	}
	printf("%lld",ans);
	
	return 0;
} 

T3 Count the Cows

它竟然是数位DP!

三观尽毁。果然还是我太弱了。

考场没做出来的主要原因是\(\lfloor\frac{x(或y)}{3^k}\rfloor\)%3与其它东西联系起来,也就是没有搞出来它的涵义。于是当时就盯着这个奇怪的式子盯了半天,百思不得其解。

其实想它的意义并不难,\(\lfloor\frac{x(或y)}{3^k}\rfloor\)是指它的三进制意义下砍掉尾巴,而砍掉尾巴之后再%3得到的就是某一位上的数字,原理类似于取出十进制数的某一位。

知道这一点之后就简单了,问题就变成了求i的个数,满足\(i\in[0,d]\),且 x+i 和 y+i 三进制意义下每一位的奇偶性都相同。然后就可以想到数位DP求解了。

说起复杂,但代码出奇简单。

#include<cstdio>
#include<cstring>
//#define zczc
#define int long long
const int S=40;
using namespace std;
inline void read(int &wh){
    wh=0;int f=1;char w=getchar();
    while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
    while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar(); }
    wh*=f;return;
}
inline int min(int s1,int s2){
	return s1<s2?s1:s2;
}
inline int max(int s1,int s2){
	return s1<s2?s2:s1;
}

int ax[S],ay[S],ad[S];
int f[S][2][2][2];
int work(int wh,bool la,bool lb,bool e){
	if(wh==0)return (la==false)&&(lb==false);
	if(f[wh][la][lb][e]>=0)return f[wh][la][lb][e];
	int ans=0,end=e?ad[wh]:2;
	for(int i=0;i<=end;i++){
		for(int sa=0;sa<=1;sa++){
			for(int sb=0;sb<=1;sb++){
				int nx=ax[wh]+i-la*3+sa;
				int ny=ay[wh]+i-lb*3+sb;
				if(nx<0||nx>2||ny<0||ny>2)continue;
				if((nx&1)!=(ny&1))continue;
				ans+=work(wh-1,sa,sb,e&&(i==ad[wh]));
			}
		}
	}
	//printf("%lld %d %d %d %lld\n",wh,la,lb,e,ans);
	return f[wh][la][lb][e]=ans;
}
inline void get(int wh,int a[]){
	for(int i=1;i<S;i++){
		a[i]=wh%3;wh/=3;
	}
}
void solve(){
	int x,y,d;
	read(d);read(x);read(y);
	get(x,ax);get(y,ay);get(d,ad);
	memset(f,-1,sizeof(f));
	printf("%lld\n",work(S-1,0,0,true));
	return;
}

signed main(){
	
	#ifdef zczc
	freopen("in.txt","r",stdin);
	#endif
	
	int test;read(test);
	while(test--)solve();
	
	return 0;
}

Day 2 数据结构专题

T1 Snow Boots G

挺简单一道题,考试的时候不知为何走火入魔了,明明有更保险的方法却想着投机取巧,实在是不应该。

如果只有一次询问,那么将原序列转化成一个01序列,其中0表示积雪深度超过靴子承受能力的点,1则相反,那么问题就变成了求最长连续0的个数,并拿这个答案与最长步数进行比较。复杂度\(O(N)\)

然后考虑多次询问。对于这些询问相互独立的题目,可以考虑使用离线算法。本题就是按每个询问的最大积雪深度进行升序排序,然后在回答询问之前动态更新一些点的01值(一开始所有点都赋为0,表示默认都无法通过),回答即可。

复杂度 \(O(NlogN+Q)\)

压行有点冲,见谅。

#include<cstdio>
#include<algorithm>
const int N=100010;
using namespace std;
inline void read(int &wh){
    wh=0;int f=1;char w=getchar();
    while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
    while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar(); }
    wh*=f;return;
}
inline int max(int s1,int s2){return s1<s2?s2:s1;}
#define lc (wh<<1)
#define rc (wh<<1|1)
#define mid (t[wh].l+t[wh].r>>1)
struct node{int l,r,ldata,rdata,data,len;bool all;}t[N<<2];
node operator +(node s1,node s2){
	node an;an.l=s1.l,an.r=s2.r,an.len=s1.len+s2.len;
	an.data=max(max(s1.data,s2.data),s1.rdata+s2.ldata);
	an.ldata=s1.all?s1.len+s2.ldata:s1.ldata;
	an.rdata=s2.all?s2.len+s1.rdata:s2.rdata;
	an.all=s1.all&&s2.all;return an;
}
void build(int wh,int l,int r){
	t[wh].l=l,t[wh].r=r,t[wh].len=t[wh].ldata=t[wh].rdata=t[wh].data=r-l+1,t[wh].all=true;
	if(l==r)return;build(lc,l,mid);build(rc,mid+1,r);return;
}
void change(int wh,int pl){
	if(t[wh].l==t[wh].r){t[wh].ldata=t[wh].rdata=t[wh].data=t[wh].all=0;return;}
	if(pl<=mid)change(lc,pl);else change(rc,pl);t[wh]=t[lc]+t[rc];return;
}
#undef lc
#undef rc
#undef mid
int m,n,now=1,an[N];
struct node1{int wh,s1,s2;}a[N],q[N];
inline bool cmp(node1 s1,node1 s2){return s1.s1<s2.s1;}
signed main(){
	read(m);read(n);
	for(int i=1;i<=m;i++){read(a[i].s1);a[i].wh=i;}
	for(int i=1;i<=n;i++){read(q[i].s1);read(q[i].s2);q[i].wh=i;}
	build(1,1,m);sort(a+1,a+m+1,cmp);sort(q+1,q+n+1,cmp);
	for(int i=1;i<=n;i++){
		while(now<=m&&a[now].s1<=q[i].s1){change(1,a[now].wh);now++;}
		an[q[i].wh]=t[1].data<q[i].s2?1:0;
	}
	for(int i=1;i<=n;i++)printf("%d\n",an[i]);
	return 0;
}

T2 No Time to Dry

应该是今天(或者说昨天?【滑稽】)考试最难的一道题了。

主要是它那个结论来得不明不白,鬼知道它是怎么想出来的,以及它的正确性怎么证明。结论如下:

对于数列A,满足\(i\le j, A_i=A_j, max\{A_k\}>A_i(k\in[i+1,j-1])\)三个条件的数对\({i,j}\)的个数为num,那么有结论说涂完这个数列所需要的步数便会是\(|A|-num\)步。

别问这个结论怎么来的,我也不知道,下午周老师评讲的时候直接告诉我们的,也没证明啊。那咱先姑且认为这个结论是正确的,再来考虑这道题。

然后就变成了一个奇怪的区间统计问题。找出区间可以用单调栈线性解决,然后对询问进行离线操作,加上树状数组维护。复杂度为 \(O(NlogN)\)

#include<cstdio>
#include<algorithm>
#include<stack>
const int N=200010;
using namespace std;
inline void read(int &wh){
    wh=0;int f=1;char w=getchar();
    while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
    while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar(); }
    wh*=f;return;
}

int m,n,a[N];
struct node{
	int wh,data;
};
stack<node>st;
struct sr{
	int wh,l,r;
}b[N],q[N];
inline bool cmp(sr s1,sr s2){
	return s1.r<s2.r;
}
int cnt,now=1,an[N];

#define lowbit (wh&-wh)
int t[N];
inline void change(int wh,int val){
	for(;wh<=m;wh+=lowbit)t[wh]+=val;
}
inline int work(int wh){
	int an=0;
	for(;wh;wh-=lowbit)an+=t[wh];
	return an;
}
#undef lowbit

signed main(){
	
	read(m);read(n);
	for(int i=1;i<=m;i++){
        read(a[i]);
	}
	st.push((node){0,-1});
	for(int i=1;i<=m;i++){
		while(st.top().data>a[i])st.pop();
		if(st.top().data==a[i]){
			b[++cnt].l=st.top().wh;b[cnt].r=i;st.pop();
		}
	    st.push((node){i,a[i]});
	}
	for(int i=1;i<=n;i++){
		read(q[i].l);read(q[i].r);q[i].wh=i;
	}
	sort(b+1,b+cnt+1,cmp);
	sort(q+1,q+n+1,cmp);
	for(int i=1;i<=n;i++){
		while(now<=cnt&&b[now].r<=q[i].r){
			change(b[now].l,1);
			now++;
		}
		an[q[i].wh]=q[i].r-q[i].l+1-(work(q[i].r)-work(q[i].l-1));
	}
	for(int i=1;i<=n;i++)printf("%d\n",an[i]);
	
	return 0;
}

T3 Cow Land

拿到题目之后非常开心,哎呀这道题我似乎看过,我知道这道题可以用“树上前缀异或和”来解决,那不就简单了吗……

于是拿了8分的好成绩。原因竟是因为忘了把两个前缀异或起来之后还要异或上lca的值。太凄惨了。

这道题主要运用了异或这个运算的特性,说具体一点,就是A^A=0,A^0=A 。于是考虑把路径上问题转化成两个前缀的问题。记住,一定要再异或上它们的lca的值!至于修改,上dfs序和线段树即可。

复杂度\(O(NlogN)\),比标准做法树链剖分\(O(NlogN^2)\)还要少一个log。真好。

#include<cstdio>
//#define zczc
const int N=100010;
const int S=20;
inline void read(int &wh){
    wh=0;int f=1;char w=getchar();
    while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
    while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar(); }
    wh*=f;return;
}

int m,n,a[N];
struct edge{
	int t,next;
}e[N<<1];
int esum,head[N];
inline void add(int fr,int to){
	esum++;
	e[esum].t=to;
	e[esum].next=head[fr];
	head[fr]=esum;
	return;
}

int cnt,b[N],f[N],s[N],id[N],size[N];
void dfs(int wh,int fa){
	f[wh]=fa;
	b[wh]=b[fa]^a[wh];
	id[wh]=++cnt;
	s[cnt]=wh;
	size[wh]=1;
	for(int i=head[wh],th;i;i=e[i].next){
		th=e[i].t;
		if(th==fa)continue;
		dfs(th,wh);
		size[wh]+=size[th];
	}
	return;
}
#define lc (wh<<1)
#define rc (wh<<1|1)
#define mid (t[wh].l+t[wh].r>>1)
struct node{
	int l,r,data;
}t[N<<2];
inline void pushnow(int wh,int val){
	t[wh].data^=val;return;
}
inline void pushdown(int wh){
	if(t[wh].data){
		pushnow(lc,t[wh].data);
		pushnow(rc,t[wh].data);
		t[wh].data=0;
	}
	return;
}
void build(int wh,int l,int r){
	t[wh].l=l,t[wh].r=r,t[wh].data=0;
	if(l==r){
		t[wh].data=b[s[l]];
		return;
	}
	build(lc,l,mid);
	build(rc,mid+1,r);
	return;
}
void change(int wh,int wl,int wr,int val){
	//printf("w:%d %d %d %d\n",wh,wl,wr,val);
	if(wl<=t[wh].l&&t[wh].r<=wr){
		pushnow(wh,val);
		return;
	}
	pushdown(wh);
	if(wl<=mid)change(lc,wl,wr,val);
	if(wr>mid)change(rc,wl,wr,val);
	return;
}
int work(int wh,int pl){
	if(pl==0)return 0;
	if(t[wh].l==t[wh].r){
		return t[wh].data;
	}
	pushdown(wh);
	if(pl<=mid)return work(lc,pl);
	else return work(rc,pl);
}
#undef lc
#undef rc
#undef mid

int d[N],c[N][25];
void init(int wh,int fa,int deep){
	c[wh][0]=fa;d[wh]=deep;
	for(int i=1;i<=S;i++){
		c[wh][i]=c[c[wh][i-1]][i-1];
	}
	for(int i=head[wh],th;i;i=e[i].next){
		th=e[i].t;
		if(th==fa)continue;
		init(th,wh,deep+1);
	}
	return;
}
inline void swap(int &s1,int &s2){
	int s3=s1;s1=s2;s2=s3;return;
}
inline int lca(int s1,int s2){
	if(d[s1]<d[s2])swap(s1,s2);
	for(int i=S;i>=0;i--){
		if(d[c[s1][i]]>=d[s2]){
			s1=c[s1][i];
		}
	}
	if(s1==s2)return s1;
	for(int i=S;i>=0;i--){
		if(c[s1][i]!=c[s2][i]){
			s1=c[s1][i];
			s2=c[s2][i];
		}
	}
	return c[s1][0];
}

signed main(){
	
	#ifdef zczc
	freopen("in.txt","r",stdin);
	#endif
	
	read(m);read(n);
	for(int i=1;i<=m;i++)read(a[i]);
	int s1,s2;
	for(int i=1;i<m;i++){
		read(s1);read(s2);
		add(s1,s2);add(s2,s1);
	}
	dfs(1,0);
	build(1,1,m);
	init(1,0,1);
	int op;
	while(n--){
		read(op);read(s1);read(s2);
		if(op==1){
			change(1,id[s1],id[s1]+size[s1]-1,a[s1]^s2);
			a[s1]=s2;
		}
		else{
			int l=lca(s1,s2);
			printf("%d\n",work(1,id[s1])^work(1,id[s2])^a[l]);
		}
	}
	
	return 0;
}
posted @ 2021-10-02 22:36  Feyn618  阅读(43)  评论(0编辑  收藏  举报