zoukankan      html  css  js  c++  java
  • 18.11.2绍一模拟赛

    T1 Alice 的幸运数

    题意

    给定n(nle 100)个64位无符号整数(有顺序)。
    我们可以对每个数取反,然后按顺序执行按位与(nbsp或者)nbsp按位或(nbsp或者)nbsp按位异或。
    求最后结果的最小值。

    分析

    我们发现要使结果最小,高位尽量取0。
    打个爆搜。。
    发现n很大的时候全是0。
    大胆猜想,无需证明!!!

    if(n12){
        printf("%d
    ",0);
        return ;
    }
    

    轻松AC。
    还是给一下证明吧。。
    我们首先发现只有与是有用的。
    就算没有发现也没有关系。
    我们发现上下两个数进行与运算,(1)的数量至少除以(2)
    很明显,上面是累计下来的结果,有一些位是(1),其他是(0)
    我们与下面的数进行与运算,发现如果剩下的(1)大于一半,我们可以对下面的数取反。
    这样子(1)的数量就减少了大于一半了。
    那么至多(2^7)就能保证答案为(0)了。

    代码

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    #include <cmath>
    #define ll long long
    #define ull unsigned long long
    #define file "lucky"
    using namespace std;
    ull read(){
    	char c;ull num,f=1;
    	while(c=getchar(),!isdigit(c))if(c=='-')f=-1;num=c-'0';
    	while(c=getchar(), isdigit(c))num=num*10+c-'0';
    	return f*num;
    }
    int n;
    ull a[109],now,ans;
    void dfs(int d){
    	if(d==n+1){
    		ans=min(ans,now);
    		return ;
    	}
    	ull tmp=now;
    	now=tmp&a[d];dfs(d+1);
    	if(ans==0)return ;
    	now=tmp&(~a[d]);dfs(d+1);
    	if(ans==0)return ;
    	now=tmp^a[d];dfs(d+1);
    	if(ans==0)return ;
    	now=tmp^(~a[d]);dfs(d+1);
    	if(ans==0)return ;
    	now=tmp|a[d];dfs(d+1);
    	if(ans==0)return ;
    	now=tmp|(~a[d]);dfs(d+1);
    	if(ans==0)return ;
    }
    void work(){
    	scanf("%d",&n);
    	if(n>12){
    		cout<<0<<endl;
    		return ;
    	}
    	ans=(1<<64)-1;
    	for(int i=1;i<=n;i++)a[i]=read();
    	now=a[1];dfs(2);
    	if(ans!=0){now=~a[1];dfs(2);}
    	cout<<ans<<endl;
    }
    int main()
    {
    	freopen(file".in","r",stdin);
    	freopen(file".out","w",stdout);
    	int Case;
    	scanf("%d",&Case);
    	while(Case--)work();
    	return 0;
    }
    

    T2Marisa 的礼物

    题意

    一开始有1元钱,有(n(nle 10^5))个置换关系,当我们拥有超过(r_i(r_ile 10^9))元钱时可以花光所有钱,用(t_i(t_ile 10^9))的时间把身上的钱换成(v_i(v_ile 10^9))元。
    问使身上的钱超过(m(mle 10^9)),最少需要多少时间。

    分析

    很明显每个置换关系只可能用一遍。
    我们可以跑背包。
    但是背包的状态有点大。
    发现其实(n)很小。
    我们把容量离散化,这样就可以把容量缩到(10^5),可以跑背包。
    (f[v])表示当身上的钱为(v)的时候,最少时间。
    (f[v_i]=min{f[k],kge r_i}+t_i)
    暴力求最小值。
    然后我们就可以(O(n^2))求出所有的价值了。

    时间复杂度不够优。
    我们继续考虑优化这个求最小值的过程。
    单调队列?
    并不是单调的。
    线段树!
    我们用线段树维护这个数组。
    就可以(O(nlogn))维护最小值了。
    也可以考虑反向线段树,这样子就不用支持区间减法了。

    另外一种思路是最短路。
    离散化之后我们把拥有的钱按从小到大排序,然后从大到小相邻连边,边权为0。
    每种置换关系对应的(r_i)向它的(v_i)连边,边权为(t_i),我们从(1)元开始跑最短路,然后就可以求出到(m)的最短路了。

    代码

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    #include <cmath>
    #include <bits/stdc++.h>
    #define ll long long
    #define file "gift"
    using namespace std;
    const int M=2e5+1000;
    struct edge{
    	int u,v;
    	ll t;
    }e[M];
    int read(){
    	char c;int num,f=1;
    	while(c=getchar(),!isdigit(c))if(c=='-')f=-1;num=c-'0';
    	while(c=getchar(), isdigit(c))num=num*10+c-'0';
    	return f*num;
    }
    int n,m,tmp[M*3],cnt;
    ll f[M*3];
    ll tree[M*20];
    bool cmp1(int a,int b){return a<b;}
    bool cmp(edge a,edge b){return a.u<b.u;}
    void update(int rt){
    	tree[rt]=min(tree[rt<<1],tree[rt<<1|1]);
    }
    void build(int l,int r,int rt){
    	if(l>r)return ;
    	if(l==r){tree[rt]=f[l];return;}
    	int mid=(l+r)>>1;
    	build(l,mid,rt<<1);
    	if(mid+1<=r)build(mid+1,r,rt<<1|1);
    	update(rt);
    }
    ll ask(int l,int r,int L,int R,int rt){
    	if(l<=L&&R<=r){return tree[rt];}
    	int mid=(L+R)>>1;
    	ll ans=1ll<<61;
    	if(mid>=l)ans=min(ans,ask(l,r,L,mid,rt<<1));
    	if(mid+1<=r)ans=min(ans,ask(l,r,mid+1,R,rt<<1|1));
    	return ans;
    }
    void change(int x,int L,int R,int rt,ll w){
    	if(L==R){tree[rt]=w;return ;}
    	int mid=(L+R)>>1;
    	if(mid>=x)change(x,L,mid,rt<<1,w);
    	else change(x,mid+1,R,rt<<1|1,w);
    	update(rt);
    }
    int fd(int x){
    	int l=1,r=cnt,mid;
    	while(l<=r){
    		mid=(l+r)>>1;
    		if(tmp[mid]==x)return mid;
    		if(tmp[mid]>x)r=mid-1;
    		else l=mid+1;
    	}
    }
    void work(){
    	cnt=0;n=read();tmp[++cnt]=m=read();
    	tmp[++cnt]=1;
    	for(int i=1;i<=n;i++){
    		tmp[++cnt]=e[i].v=read();
    		tmp[++cnt]=e[i].u=read();
    		e[i].t=read();
    		if(e[i].u>=e[i].v){
    			n--;i--;
    			continue;
    		}	
    	}
    	sort(tmp+1,tmp+1+cnt,cmp1);
    	sort(e+1,e+1+n,cmp);
    	cnt=unique(tmp+1,tmp+1+cnt)-(tmp+1);
    	for(int i=1;i<=n;i++){
    		e[i].v=fd(e[i].v);
    		e[i].u=fd(e[i].u);
    	}
    	memset(f,0x3f,sizeof(f));
    	m=fd(m);f[1]=0;
    	build(1,cnt,1);
    	//cout<<tree[3]<<endl;
    	//cout<<ask(3,cnt,1,cnt,1)<<endl;
    	ll minn;
    	for(int i=1;i<=n;i++){
    		minn=(1<<31)-1;
    		minn=ask(e[i].u,cnt,1,cnt,1);
    		if(minn+e[i].t<f[e[i].v]){
    			f[e[i].v]=minn+e[i].t;
    			change(e[i].v,1,cnt,1,minn+e[i].t);
    		}
    	}
    	minn=(1<<31)-1;
    	minn=ask(m,cnt,1,cnt,1);
    	printf("%lld
    ",((minn<(1ll<<61))?minn:-1));
    }
    int main()
    {
    	freopen(file".in","r",stdin);
    	freopen(file".out","w",stdout);
    	int Case=read();
    	while(Case--)work();
    	return 0;
    }
    /* 发现每次换钱必须变得更多,那么先去除那些变少的边。
     * 可以离散化。
     * 我们对起点进行排序,然后升序跑一遍dp。 
     * 考虑线段树维护最小值,单点修改。 
     * f[v]=min{min{f[u]}+t}; 
     * 时间复杂度
     * 离散化nlogn,线段树logn。  
     * 循环n
     * 总复杂度O(nlogn) 
     */ 
    

    Marisa 的排序

    题意

    定义一种区间排序。
    排序的方式是将区间内下标(iequiv x(mod d))的放在一块,块的顺序按照块内第一个数的下标排序。
    比如(0,1,2,3,4,5,6)(d=3)时排序后变成(0,3,6,1,4,2,5)
    给定一个字符串,和(m)个操作。
    每次操作给定区间长度(k)(d)
    从左到右给每一个长度为(k)的序列以此排序。
    每一个操作之后输出现在的字符串。

    分析

    乍一看无从下手,但是发现每次操作的时候,区间内的数字变化是一定的。
    也就是说,我们只要用一个(p)数组表示一次操作数字的位置变化,就能很轻松地对每一个区间进行递推。
    时间复杂度为(O(mn^2)),显然是无法通过所有的数据的。

    我们考虑优化这个递推过程。
    我们可以把排序区间移动看成是数组整体左移,这样子我们只要变更一下(p)数组,在用(p)数组递推的同时考虑数组左移,就只要不停地对第一个区间进行排序即可。
    比如(k=4,d=2),原数组为(0,1,2,3,4,5)的时候。
    我们原来的(p)数组应该是(0,2,1,3,4,5)
    现在我们只要把(p)数组变成(2,1,3,4,5,0)就可以考虑整体左移了。

    我们可以利用矩阵乘法的思想优化这个递推过程。
    定义数组乘法为按照数组递推一次。
    我们发现对字符串的递推与对(p)数组自乘是一样的。
    那么我们快速幂计算(p)数组递推(n-k+1)次的数组,再用它去乘字符串,就可以把(O(n))的递推优化到(O(logn))的递推了。
    注意我们用(p)数组递推的时候原字符串发生了左移,在结束一次操作后一定要把它右移回来。

    代码

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    #include <cmath>
    #define ll long long
    #define file "sorting"
    using namespace std;
    const int Maxn=1000009;
    int read(){
    	char c;int num,f=1;
    	while(c=getchar(),!isdigit(c))if(c=='-')f=-1;num=c-'0';
    	while(c=getchar(), isdigit(c))num=num*10+c-'0';
    	return f*num;
    }
    int n,m,k,d;
    char c[Maxn],w[Maxn];
    int cnt[Maxn],ans[Maxn];
    int p[Maxn],tmp[Maxn],l[Maxn];
    void multi(int *a,int *b){
    	for(int i=0;i<n;i++)
    		tmp[i]=a[b[i]];
    	for(int i=0;i<n;i++)
    		a[i]=tmp[i];
    }
    void Pow(int *a,int p){
    	for(int i=0;i<n;i++)ans[i]=i;
    	for(;p;p>>=1,multi(a,a))
    		if(p&1)multi(ans,a);
    	for(int i=0;i<n;i++)a[i]=ans[i];	
    }
    void work(){
    	k=read();d=read();
    	memset(cnt,0,d*4);
    	for(int i=0;i<k;i++)
    		cnt[i%d]++;
    	for(int i=0;i<d;i++)
    		cnt[i]+=cnt[i-1];
    	for(int i=k-1;i>=0;i--)
    		p[(cnt[i%d]--)-1]=i;
    	for(int i=k;i<n;i++)
    		p[i]=i;
    	multi(p,l);
    	//for(int i=0;i<n;i++)printf("%d ",p[i]);
    	//printf("
    ");
    	int t=n-k+1;
    	Pow(p,t);
    	for(int i=0;i<n;i++)
    		w[i]=c[p[i>=t?i-t:i+n-t]];
    	puts(w);
    	for(int i=0;i<n;i++)
    		c[i]=w[i];
    }
    int main()
    {
    	while((c[n]=getchar())!='
    ')n++;
    	m=read();
    	for(int i=0;i<n;i++)l[i]=i+1;
    	l[n-1]=0;
    	while(m--)work();
    	return 0;
    }
    /* p为置换矩阵
     * l为左移矩阵  
     */ 
    
    
    
  • 相关阅读:
    odoo12安装Wkhtmltopdf打印出pdf已损坏
    odoo10实现单点登陆绕过登陆集成页面
    odoo添加顶部按钮实现自定义方法
    odoo t标签用法
    C#编写dll进行sql server数据库扩展储存过程
    小程序生成二维码(使用binarywang封装的微信工具包)
    -bash: ./start.sh: /bin/sh^M: bad interpreter: No such file or directory 错误解决方案
    过滤器跟拦截器的区别
    mybatis-DATE_FORMAT() 时间格式化,所对应的时间格式
    解决 MyBatis-Plus 更新对象无法设空值
  • 原文地址:https://www.cnblogs.com/onglublog/p/9895789.html
Copyright © 2011-2022 走看看