• 20201017校测


    这篇博客为什么没有设置密码呢?懂的都懂

    T1

    description:

    给定一个N*M的矩阵,每一个格子中有一个正整数,求所有子矩阵个数满足子矩阵内所有数的和是给定值k的倍数

    data range:

    (1le N,Mle 400)
    (kle 10^6)

    solution:

    还算比较有趣吧
    首先可以想到枚举所有子矩形判断,用二维前缀和优化
    时间复杂度(O(n^4))
    然后似乎就遇到了瓶颈
    可以先考虑一维的情况
    处理所有前缀和,那么如果两个前缀和对k取模后相等,就存在一个区间其和为k的倍数
    那么放到二维上,就是(n^2)枚举后就可以按照一维来做了
    p.s.考场上狂取模,结果100->60 T_T

    code:

    #include<bits/stdc++.h>
    using namespace std;
    const int N=405,M=1e6+5;
    int n,m,k,a[N][N],s[N][N],cnt[M];
    long long ans;
    inline int read()
    {
    	int s=0,w=1; char ch=getchar();
    	for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
    	for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
    	return s*w;
    }
    inline int dec(int x,int y){x-=y;return x<0?x+k:x;}
    int main()
    {
    	n=read(),m=read(),k=read();
    	for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)a[i][j]=read()%k;
    	for(int i=1;i<=n;++i)
    		for(int j=1;j<=m;++j)
    			s[i][j]=((s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j])%k+k)%k;
    	for(int i=1;i<=m;++i)
    		for(int j=i;j<=m;++j)
    		{
    			cnt[0]=1;
    			for(int w=1;w<=n;++w)
    			{
    				int p=dec(s[w][j],s[w][i-1]);
    				++cnt[p];
    			}
    			for(int w=1;w<=n;++w)
    			{
    				int p=dec(s[w][j],s[w][i-1]);
    				ans+=1ll*cnt[p]*(cnt[p]-1)/2ll,cnt[p]=0;
    			}
    		}
    	cout<<ans;
    	return 0;
    }
    

    T2

    description:

    给定一个N个节点的树,要求选择尽量少的点染色后满足树上任意一个点和最近的染色点之间的距离(le k),其中k为定值

    data range:

    (Nle 10^5)
    (kle 20)

    solution:

    这个题目的数据特别哄人
    明明存在(O(n))做法,偏偏(Nle 10^5)
    明明标准算法和(k)关系不大,偏偏(kle 20)
    吐槽完毕(明明是自己没想到还怪别人)
    这是一道贪心题
    我们钦定1为树根,转无根树为有根树
    然后从最深的节点往上跳k级父亲,在k级父亲处染色(这样是在保证最深节点被覆盖的情况下覆盖尽量多的节点)
    重复此过程,直到所有节点均被覆盖
    考完后突然发现我几乎做过原题(网络(Network, Seoul 2007, LA3902))。。。怎么还是挂了
    p.s.双倍经验

    code:

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e5+5;
    int n,k,t,mx,fa[N];
    vector<int>e[N],node[N];
    bool bc[N];
    inline int read()
    {
    	int s=0,w=1; char ch=getchar();
    	for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
    	for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
    	return s*w;
    }
    void dfs(int u,int pr,int dep)
    {
    	node[dep].push_back(u);mx=max(mx,dep);
    	for(int i=0;i<e[u].size();++i)
    	{
    		int v=e[u][i];
    		if(v==pr)continue;
    		fa[v]=u;dfs(v,u,dep+1);
    	}
    }
    void tu(int u,int pr,int d)
    {
    	bc[u]=true;
    	if(!d)return;
    	for(int i=0;i<e[u].size();++i)
    	{
    		int v=e[u][i];
    		if(v==pr)continue;
    		tu(v,u,d-1);
    	}
    }
    int main()
    {
    	n=read();k=2;
    	for(int i=2;i<=n;++i)
    	{
    		int u=read();
    		e[u].push_back(i),e[i].push_back(u);
    	}
    	fa[1]=1;dfs(1,0,1);int ans=0;
    	for(int i=mx;i;--i)
    		for(int j=0;j<node[i].size();++j)
    		{
    			int nd=node[i][j],u=k;
    			if(bc[nd])continue;
    			while(u--)nd=fa[nd];
    			tu(nd,0,k);++ans;
    		}
    	printf("%d
    ",ans);
    	return 0;
    }
    

    T3

    description:

    一个长度为N的01串,每次操作可以对规定区间长度(种类数为K)的0,1进行取反。问最少用多少次才能将串的每一位变为1。

    data range:

    (Nle 4*10^4)
    (Kle 64)

    solution:

    此题甚妙
    考虑对一段区间进行取反操作实际上就是在差分后的序列上的两个端点处取反
    当然这里的差分是指异或差分
    具体来说就是这样:
    如果原序列为(111111)
    那么它的差分序列为(1000001)
    倘若我们要翻转区间([2,3])
    原序列变为(100111)
    差分序列变为(1101001)
    具体证明可以参考普通的差分
    于是现在问题就转化为每次可以对规定长度两端的数字取反,询问最后全部为0需要进行多少次操作
    (其中1的个数显然不超过16)
    显然地,每次取反的两个数必须至少有一个为1
    如果是一个0一个1,那么就相当于将1移动到0的位置
    如果两个均为1,那么就相当于两个1相互抵消
    我们可以先预处理出某个1到达序列上任意位置的最少步数
    然后在此基础上就可以求出将任意两个1消掉所需要的最少步数
    到最后,问题就转化为一个最优匹配问题
    直接状压dp即可
    p.s.看了题解发现原来状压也可以用广搜来实现

    code:

    #include<bits/stdc++.h>
    using namespace std;
    const int N=4e4+5,T=65,M=18;
    int n,k,m,cnt;
    int a[N],st[T],pos[M],dis[M][N],pw[M],dist[M][M],dp[1<<M];
    bool inq[1<<M];
    inline int read()
    {
    	int s=0,w=1; char ch=getchar();
    	for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
    	for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
    	return s*w;
    }
    inline void bfs(int p)
    {
    	queue<int>q;q.push(pos[p]);
    	while(!q.empty())
    	{
    		int u=q.front();q.pop();
    		for(int i=1;i<=m;++i)
    		{
    			int v=u+st[i];
    			if(v>n+1||dis[p][v]<=dis[p][u]+1)goto out;
    			dis[p][v]=dis[p][u]+1;q.push(v);
    			out:;v=u-st[i];
    			if(v<1||dis[p][v]<=dis[p][u]+1)continue;
    			dis[p][v]=dis[p][u]+1;q.push(v);
    		}
    	}
    }
    int main()
    {
    	memset(dis,0x3f,sizeof(dis));
    	memset(dp,0x3f,sizeof(dp));
    	n=read(),k=read(),m=read();
    	for(int i=1;i<=k;++i)a[read()]=1;
    	for(int i=1;i<=m;++i)st[i]=read();
    	for(int i=n+1;i;--i)a[i]^=a[i-1];
    	for(int i=1;i<=n+1;++i)
    		if(a[i])pos[++cnt]=i,dis[cnt][i]=0;
    	for(int i=1;i<=cnt;++i)bfs(i);
    	pw[0]=1;for(int i=1;i<=cnt;++i)pw[i]=pw[i-1]<<1;
    	for(int i=1;i<=cnt;++i)
    		for(int j=i+1;j<=cnt;++j)
    			dist[i][j]=dist[j][i]=min(dis[i][pos[j]],dis[j][pos[i]]);
    	queue<int>q;
    	dp[pw[cnt]-1]=0;q.push(pw[cnt]-1);inq[pw[cnt]-1]=1;
    	while(!q.empty())
    	{
    		int u=q.front();q.pop();inq[u]=0;
    		for(int i=0;i<cnt;++i)
    			if(u&pw[i])
    			{
    				for(int j=i+1;j<cnt;++j)
    					if(u&pw[j])
    					{
    						int v=u^pw[i]^pw[j];
    						if(dp[v]<=dp[u]+dist[i+1][j+1])continue;
    						dp[v]=dp[u]+dist[i+1][j+1];
    						if(!inq[v])q.push(v),inq[v]=1;
    					}
    				break;
    			}
    	}
    	cout<<dp[0];
    	return 0;
    }
    
  • 相关阅读:
    C++ list<list<int> >类型的对象遍历
    Apache与Nginx服务器对比
    服务器重写技术:rewrite
    冒泡排序(python版)
    有k个list列表, 各个list列表的元素是有序的,将这k个列表元素进行排序( 基于堆排序的K路归并排序)
    堆排序(C++版)
    [转载] 单链表的相关操作
    TCP三次握手连接与四次握手断开
    [转载] TCP与UDP对比
    进程与线程的联系与区别
  • 原文地址:https://www.cnblogs.com/zmyzmy/p/13831910.html
Copyright © 2020-2023  润新知