18.11.2绍一模拟赛

T1 Alice 的幸运数

题意

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

分析

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

if(n12){
    printf("%d\n",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(n\le 10^5)\)个置换关系,当我们拥有超过\(r_i(r_i\le 10^9)\)元钱时可以花光所有钱,用\(t_i(t_i\le 10^9)\)的时间把身上的钱换成\(v_i(v_i\le 10^9)\)元。
问使身上的钱超过\(m(m\le 10^9)\),最少需要多少时间。

分析

很明显每个置换关系只可能用一遍。
我们可以跑背包。
但是背包的状态有点大。
发现其实\(n\)很小。
我们把容量离散化,这样就可以把容量缩到\(10^5\),可以跑背包。
\(f[v]\)表示当身上的钱为\(v\)的时候,最少时间。
\(f[v_i]=min{f[k],k\ge 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\n",((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 的排序

题意

定义一种区间排序。
排序的方式是将区间内下标\(i\equiv 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("\n");
	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')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为左移矩阵  
 */ 


posted @ 2018-11-02 13:49  _onglu  阅读(203)  评论(0编辑  收藏  举报