• [提高组互测] Day1


    Poman Numbers

    题目描述

    点此看题

    解法

    以后做不出来第一题一定要打表找规律,这么辣鸡的题我空耗了两个小时

    你发现每个数前面的符号是正或者负,打表发现最后一个位置的符号一定为正,倒数第二个位置的符号一定为负,其他位置的符合任填,构造方法:

    因为已经知道结论了我们这里就用归纳法:

    • 如果只有一个 +,符合条件,直接退出。
    • 如果除了最后一个都是 -,那么把前面的依次拿出来即可,符合条件。
    • 否则找到倒数第二个 +,设它的位置为 (p),那么把 (|1,p+1|,|p+2,|S||) 划分,因为 (|1,p+1|) 这一段的正负序列为 ..-+,取反后变成 ..+-,正好和原序列对应,归纳到了更小的情况。

    现在的问题是判断一堆二的幂次添上正负号之后能否凑出某个数,发现这是一个二选一决策问题。套路是把它转化为 (01) 决策问题,设物品大小集合为 (a_i),先把 (m+sum a_i),然后决策是否减去 (2a_i),因为物品存在倍数关系,所以把它从大到小排序之后贪心决策即可。

    #include <cstdio>
    #include <algorithm>
    using namespace std; 
    const int M = 100005;
    #define int long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,a[M],vs[200];char s[M];
    signed main()
    {
    	n=read();m=read();scanf("%s",s+1);
    	for(int i='a';i<='z';i++)
    		vs[i]=(1<<i-'a');
    	m+=vs[s[n-1]]-vs[s[n]];
    	for(int i=1;i<n-1;i++)
    	{
    		a[i]=vs[s[i]];
    	    m+=a[i];
    	}
    	sort(a+1,a+n-1);
    	for(int i=n-2;i>=1;i--)
    		if(m>=2*a[i]) m-=2*a[i];
    	if(m==0) puts("Yes");
    	else puts("No");
    }
    

    Replace by MEX

    题目描述

    点此看题

    解法

    因为 (mex) 函数的性质,我们考虑把它变成 ([0,1,2...n-1]) 的序列。

    因为操作数很少我们尽量一步到位,若当前的 (mex<n),我们把 (a[mex+1]leftarrow mex),否则我们找到任意一个 (a[i] ot=i-1),那么把 (a[i]leftarrow n),循环进行以上操作即可。

    如果触发第二种操作,那么接下来会触发两次第一种操作,所以操作上界是 (frac{3n}{2}) 的。

    #include <cstdio>
    #include <queue>
    using namespace std;
    const int M = 100005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int T,n,m,a[M],ans[4*M],num[4*M],sum[4*M];
    void ins(int i,int l,int r,int id,int f)
    {
    	if(l==r)
    	{
    		num[i]+=f;
    		sum[i]=(num[i]>0);
    		return ;
    	}
    	int mid=(l+r)>>1;
    	if(mid>=id) ins(i<<1,l,mid,id,f);
    	else ins(i<<1|1,mid+1,r,id,f);
    	sum[i]=sum[i<<1]+sum[i<<1|1];
    }
    int mex(int i,int l,int r)
    {
    	if(l==r) return l;
    	int mid=(l+r)>>1;
    	if(sum[i<<1]<mid-l+1) return mex(i<<1,l,mid);
    	return mex(i<<1|1,mid+1,r);
    }
    void op(int u)
    {
    	int t=mex(1,0,n);
    	ins(1,0,n,a[u],-1);
    	ans[++m]=u;a[u]=t;
    	ins(1,0,n,a[u],1);
    }
    void work()
    {
    	n=read();m=0;
    	queue<int> q;
    	for(int i=1;i<=4*(n+1);i++)
    		sum[i]=num[i]=0;
    	for(int i=1;i<=n;i++)
    	{
    		a[i]=read();
    		ins(1,0,n,a[i],1);
    		if(a[i]!=i-1) q.push(i);
    	}
    	while(!q.empty())
    	{
    		int u=q.front();
    		if(a[u]==u-1)
    		{
    			q.pop();
    			continue;
    		}
    		int t=mex(1,0,n);
    		if(t==n) op(u);
    		else op(t+1);
    	}
    	printf("%d
    ",m);
    	for(int i=1;i<=m;i++)
    		printf("%d ",ans[i]);
    	puts("");
    }
    signed main()
    {
    	T=read();
    	while(T--) work();
    }
    

    五彩斑斓的世界

    题目描述

    点此看题

    解法

    前置技巧:如何维护一个值域中 (x) 变成 (y) 的操作,首先把所有相同的元素连起来,每个值对应到原序列的一个下标。那么如果 (y) 存在直接合并并查集,否则直接把值 (y) 对应过去。


    因为本题值域很小我们考虑对于每个块维护值域,那么减法操作相当于把后面一段值域平移到前面去,如果后面更长,那么把前面移动到后面,然后打上整体减法标记即可;否则直接把后面移动到前面,就使用前置技巧维护即可。因为花多少时间值域长度就减少多少,所以均摊的复杂度是 (O(1e5))

    但是本题要卡空间,我们把询问离线下来,然后对于每个块单独修改并考虑其贡献,时间复杂度 (O((n+q)sqrt n))

    总结

    待修的分块常常和势能法有关,我目前见到的有:设置域使得花多少时间域就减少多少、修改很少的次数时需要暴力重构,否则可以通过打标记的方式解决。

    //They're the reason that I still am who I am
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <cmath>
    using namespace std;
    const int M = 1000005;
    inline int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    inline void write(int x)
    {
    	if(x<=9)
    	{
    		putchar(x+'0');
    		return ;
    	}
    	write(x/10);
    	putchar(x%10+'0');
    }
    int n,m,k,Q,lz,mx,a[M],fa[M],nm[M],val[M],siz[M];
    int L[M],R[M],ans[M];struct node {int op,l,r,x;}q[M];
    inline int find(int x)
    {
    	if(x!=fa[x]) fa[x]=find(fa[x]);
    	return fa[x];
    }
    inline void build(int w)//rebuilt the block
    {
    	mx=lz=0;//clear all
    	for(int i=L[w];i<=R[w];i++) mx=max(mx,a[i]);
    	for(int i=L[w];i<=R[w];i++)
    	{
    		val[i]=0;
    		if(!nm[a[i]])
    		{
    			nm[a[i]]=i;
    			val[i]=a[i];
    			fa[i]=i;
    		}
    		else fa[i]=nm[a[i]];
    		siz[a[i]]++;
    	}
    }
    inline void change(int x,int y)//value x->y
    {
    	if(nm[y])
    	{
    		fa[nm[x]]=nm[y];
    		siz[y]+=siz[x];
    	}
    	else
    	{
    		val[nm[y]=nm[x]]=y;
    		siz[y]+=siz[x];
    	}
    	nm[x]=siz[x]=0;//good habits
    }
    inline void upd(int x)//sub block x
    {
    	if(x+lz>mx) return ;//useless
    	if(x>(mx-lz)/2)//move back to front
    	{
    		for(int i=lz+x+1;i<=mx;i++)
    			if(nm[i]) change(i,i-x);
    		mx=min(mx,lz+x);//change the upper bound
    	}
    	else//move front to back(give tags)
    	{
    		for(int i=lz;i<=lz+x;i++)
    			if(nm[i]) change(i,i+x);
    		lz+=x;
    	}
    }
    inline void bruteup(int w,int l,int r,int x)//brute update
    {
    	int ll=max(l,L[w]),rr=min(r,R[w]);
    	//attention the range limit
    	for(int i=L[w];i<=R[w];i++)
    	{
    		int t=val[find(i)];
    		a[i]=t-lz;
    		nm[t]=siz[t]=0;
    	}
    	for(int i=ll;i<=rr;i++)
    		if(a[i]>x) a[i]-=x;
    	build(w);
    }
    inline int bruteas(int w,int l,int r,int x)//brute ask
    {
    	int ll=max(l,L[w]),rr=min(r,R[w]),res=0;
    	for(int i=ll;i<=rr;i++)
    		if(val[find(i)]-lz==x) res++;
    	return res;
    }
    void print(int w)
    {
    	for(int i=L[w];i<=R[w];i++)
    		printf("%d ",val[find(i)]-lz);
    	puts("");
    }
    signed main()
    {
    	n=read();Q=read();m=sqrt(n);
    	for(int i=1;i<=n;i++)
    	{
    		a[i]=read();
    		int w=(i-1)/m+1;
    		if(!L[w]) L[w]=i;
    		R[w]=i;
    	}
    	int t=(n-1)/m+1;
    	for(int i=1;i<=Q;i++)
    	{
    		q[i].op=read();q[i].l=read();
    		q[i].r=read();q[i].x=read();
    	}
    	for(int w=1;w<=t;w++)//every block
    	{
    		//attention the time of clearing
    		for(int i=0;i<=1e5+1;i++)
    			nm[i]=siz[i]=0;
    		build(w);
    		for(int i=1;i<=Q;i++)
    		{
    			int op=q[i].op,l=q[i].l,r=q[i].r,x=q[i].x;
    			if(l>R[w] || L[w]>r) continue;
    			if(op==1)
    			{
    				if(l<=L[w] && R[w]<=r) upd(x);
    				else bruteup(w,l,r,x);
    			}
    			else
    			{
    				if(x+lz>mx) continue ;
    				if(l<=L[w] && R[w]<=r)
    					ans[i]+=siz[x+lz];
    				else
    					ans[i]+=bruteas(w,l,r,x);
    			}
    		}
    	}
    	for(int i=1;i<=Q;i++) if(q[i].op==2)
    		write(ans[i]),puts("");
    }
    

    New Year and Binary Tree Paths

    题目描述

    点此看题

    解法

    考虑简化问题,首先考虑单链的情况。

    假设有一条从点 (x) 一条长度为 (h) 一直向左的单链,那么这一条链的贡献是:

    [sum_{i=0}^{h-1}2^ix=(2^h-1)x ]

    从下到上的第 (iin[1,h)) 的点是向右走得到的会带来独立的贡献:(sum_{j=0}^{i-1}2^j=2^i-1)

    假设 (h) 已经确定,那么起点只能是 (x=lfloorfrac{s}{2^h-1} floor),因为 (x-1) 往右一直走 (h<x) 往左一直走 (h),它们的取值范围是不重的,因为 (h)(log) 级的,所以可能的 (x) 也只有 (log) 个。


    有分叉的情况就是把两个单链合起来呗,假设左边链长 (h_1),右边链长 (h_2),那么两条链都向左的和是:

    [(2^{h_1}+2^{h_2}-3)x+2^{h2-1}-1 ]

    同理这个 (x) 也是确定的,那么我们可以知道剩下的和 (ret)

    如果中间某一步向右走,那么带来的贡献是 (2^{1}-1,2^{2}-1...2^{h_1-2}-1)(2^{1}-1,2^{2}-1...2^{h_2-2}-1),为了更好做这个 (0/1) 规划问题,我们把后面那个 (1) 去掉,枚举选出个数即可转化成二进制数位规划问题

    (dp[i][j][k]) 表示前 (i) 个位选了 (j) 个数,这一位是否进位的方案数,暴力转移即可,时间复杂度 (O(log^5))

    总结

    对于难以计算的代价,可以考虑一种特殊情况,其他情况都从这种情况修改得来(怎么感觉我写过这句话?!)

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    using namespace std;
    const int M = 105;
    #define int long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,ans,pw[M],dp[2][M][2];
    int cal(int n,int a,int b,int t)
    {
    	int nw=0;
    	memset(dp[nw],0,sizeof dp[nw]);
    	dp[0][0][0]=1;
    	for(int i=1;i<=m;i++)//attention the upper bound 
    	{
    		if((1ll<<i)>n) break;
    		int d=(n>>i)&1;nw^=1;
    		memset(dp[nw],0,sizeof dp[nw]);
    		for(int j=0;j<=t;j++)
    		for(int k=0;k<2;k++) if(dp[nw^1][j][k])
    		for(int x=0;x<2;x++) if(!x || i<a)
    		for(int y=0;y<2;y++) if(!y || i<b)
    		if((x+y+k)%2==d)
    			dp[nw][x+y+j][(x+y+k)/2]+=dp[nw^1][j][k];
    	}
    	return dp[nw][t][0];
    }
    signed main()
    {
    	n=read();pw[0]=1;
    	while(pw[m]<=n) {pw[m+1]=pw[m]*2;m++;}
    	for(int i=1;i<=m;i++) for(int j=1;j<=m;j++)
    	{
    		if(n-pw[j-1]+1<0) continue;
    		int x=(n-pw[j-1]+1)/(pw[i]+pw[j]-3);
    		if(x<=0) continue;
    		int ret=(n-pw[j-1]+1)-x*(pw[i]+pw[j]-3);
    		for(int k=0;k<i+j-1;k++) if(!((ret+k)&1))
    			ans+=cal(ret+k,i-1,j-1,k);
    	}
    	printf("%lld
    ",ans);
    }
    
  • 相关阅读:
    adb获取不了设备List of devices attached
    Appium常用的API函数
    Clevo P950笔记本加装4G模块
    “CNKI 中国知网 PDF 全文下载”油猴脚本在线安装地址
    使用XTU降低CPU功耗,自动执行不失效
    Clevo P950系列拆机
    Win10的WSL很好用呀
    ubuntu下opencv使用cvNamedWindow()和cvShowImage()出错的解决方法
    2017年研究生数学建模D题(前景目标检测)相关论文与实验结果
    [翻译]怎么阅读一篇论文
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15398834.html
Copyright © 2020-2023  润新知