Fork me on GitHub

清北学堂

qsing1

1.低仿机器人

一道大模拟

2.放爆竹

小辉原本想让小明告诉他,如果同时点燃n串雷,最多会有多长的时间至少有两串雷爆炸的声音是一样的。

但是小辉觉得这个问题真是太简单了,所以决定问小明,如果在山谷中(有回音)同时点燃n串雷,那最多会有多长的时间至少有两串雷爆炸的声音是一样的呢?

小辉认为一枚春雷或者地雷爆炸都需要1ms,且山谷中的回音不减弱,并且小辉给出的雷串不会是任意一个雷串的重复(不管重复的雷串是否存在,即无论如何都不会存在类似于01010101的雷串)。

思路:可以用tire树,但需要证明扫到1000以内就可以

代码:

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
int tr[20005005][2],n,tot,ans;
char s[2000];
void insert()
{
	int k=0;
	for(int i=0;i<=1000;i++)
	{
		int to=s[i]-'0';
		if(!tr[k][to])tr[k][to]=++tot;
		k=tr[k][to];
	}
}
int ask()
{
	int p=0,res=0;
	for(int i=0;i<=1000;i++)
	{
		int to=s[i]-'0';
		if(!tr[p][to])return res;
		else p=tr[p][to],res++;
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s);
		int len=strlen(s),cnt = 0;
		for(int j=len;j<=1000;j++)
		{
			s[j]=s[cnt];
			cnt=(cnt+1)%len;
		}
		ans=max(ans,ask()); insert();
	}
	printf("%d\n",ans);
	return 0;
}

qsing2

1.是一道物理题,分三种情况,从高到低,从低到高,平跳,推式子即可

2.回声也就说明重复,可证最多重复一千次,然后建一棵Tire树,每次加入前查询,不必配直接返回即可。

3.线段树,当前点若没有人则赋为inf,单点修改,最后一问,可维护一下区间有多少学生,线段树区间求和即可,注意最后要从tail向前扫,因为他到队尾后还可能向队尾走,而且不一定总的队长为n+m,可能更长,而且注意build传参的范围,不是n,而是n+m.

#define N 20050
using namespace std;
int tr[20005005][2];
char s[2000];
int ans,tot,n;
inline void Insert(){
	int k = 0;
	for(int i = 0;i <= 1000;i ++){
		int o = s[i] - '0';
		if(!tr[k][o]) tr[k][o] = ++ tot;
		k = tr[k][o];
	}
}
inline int Ask() {
	int k = 0,res = 0;
	for(int i = 0;i <= 1000;i ++) {
		int o = s[i] - '0';
		if(!tr[k][o]) {
			return res;
		}
		else k = tr[k][o],res ++;
	}
	return res;
}
int main() {
	scanf("%d",&n);
	for(int i = 1;i <= n;i ++) {
		scanf("%s",s);
		int len = strlen(s),cnt = 0;
		for(int j = len;j <= 1000;j ++)
			s[j] = s[cnt],cnt = (cnt + 1) % len;
		ans = max(ans,Ask()); Insert();
	}
	printf("%d\n",ans);
	fclose(stdin); fclose(stdout);
	return 0;
}

qsing3

1.网址压缩--哈希,随机化

更具体来说,网址压缩的目标是希望将较长的网址变为更短的网址,例如我们可以将https://www.baidu.com压缩为 http://sb.cn/。为了更加形式化我们的问题,我们现在的任务是给定N个只包含小写字母的字符串,你需要输出对这N个字符串进行压缩的结果。 你可以使用任意的压缩算法,但你需要保证满足如下的性质:

  1. 压缩的结果字符串仍然只有小写字母。

  2. 压缩的结果不能是空串,且长度必须要比原来的字符串短。

  3. 相同的字符串压缩结果必须相同,不同的字符串压缩结果必须不同。 任意满足上述条件的压缩方法都是的,所以你的目标就是对给定的N个字符串进行压缩。数据保证有解。

    代码:

    #include<cstdio>
    #include<map>
    #include<cstdlib>
    #include<iostream>
    #include<cstring>
    #include<ctime>
    using namespace std;
    int n,tot;
    map<string,bool>mp,vis;
    map<string,string>ans;
    map<string,int>hao;
    char s[1005][55];
    int read()
    {
    	int x=0,f=1;
    	char ch=getchar();
    	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    void solve(int x)
    {
    	vis[s[x]+1]=1;
    	int len=strlen(s[x]+1);
    	char pi[1005];
    	while(1)
    	{
    		int cnt=0;
    		for(int i=1;i<=len-1;i++)
    		{
    			pi[i]=(rand()*rand())%27+97;
    		}
    		if(mp[pi+1]!=1)
    		{
    			mp[pi+1]=1;
    			ans[s[x]+1]=pi+1;
    			break;
    		}
    	}
    	for(int i=1;i<=len-1;i++)printf("%c",pi[i]);
    	puts("");
    }
    int main()
    {
    //	freopen("url.in","r",stdin);
    //	freopen("url.out","w",stdout);
    	n=read();
    	srand(time(0));rand();
    	for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
    	for(int i=1;i<=n;i++)
    	{
    		if(vis[s[i]+1])cout<<ans[s[i]+1]<<endl;
    		else solve(i);
    	}
    	fclose(stdin);fclose(stdout);
    	return 0;
    }
    /*
    4
    jzm
    bilibili
    hhhhh
    jzm
    */
    

2.异构体--宽搜

然而,随着岁月的流逝, 有不法分子开始对Paradeus的教义发动了攻击。不法分子在Paradeus的教义上添加了一条记录(a,b),代表b是由a介绍入教的。 这条记录的加入导致Nyto们发现教义已经不合法了。 为了复兴教义,教徒们决定找到这条被不法分子加入的记录,并将其删除以恢复教义的荣光。

更具体的说,现在给定N对记录(ai, bi)代表ai是将bi拉入教的。注意这N条记录包含了被不法分子添加的那一条。现在我们希望你找到某一条记录,使得删掉这条记录之后剩下的N−1条记录能够形成合法的教义。要注意的是, 教义并没有标注Mercurows,所以任何人都有可能是Mercurows。

思路:分两种情况,第一是有没有入度为零的点,若果有,则他为根,找入度为二的点,枚举他的所有出边,看删去之后图是否联通

第二是若没有,则扫描整张图,枚举删哪个边,图既联通,而且边的权值最大,可以倒着扫,第一次符合后即跳出

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#include <cstring>
#include <map>
using namespace std;
struct node
{
	int u,v,nxt;
}e[100005];
int flag, head[100005], cnt, a, b, n, du[100005], uu, vv, ans, tot;
bool vis[100005];
void add(int x, int y)
{
	e[++cnt].u = x;
	e[cnt].v = y;
	e[cnt].nxt = head[x];
	head[x] = cnt;
}
struct edge
{
	int u, v;
}ee[100005];
void dfs(int x)
{
	tot ++;
	if(flag == 1) return ;
	vis[x] = 1;
	for(int i = head[x];i ; i = e[i].nxt)
	{
		int to = e[i].v;
		if(x == uu && to == vv) continue;
		if(vis[to])
		{
			flag = 1;
			return ;
		} 
		dfs(to);
	}
}
signed main()
{
// 	freopen("remove.in", "r", stdin);
// 	freopen("remove.out", "w", stdout);
	scanf("%d" ,&n);
	for(int i = 1; i <= n; i ++)
	{
		scanf("%d%d", &a, &b);
		add(a, b);
		ee[i].u = a, ee[i].v = b;
		du[b] ++;
	}
	int rt = 0;
	for(int i = 1; i <= n; i ++)
		if(du[i] == 0) rt = i; 
	if(rt != 0)
	{
		for(int i = n; i >= 1; i --)
		{
			if(du[ee[i].v] != 2) continue;
			else
			{
				for(int j = 1; j <= n; j ++) vis[j] = 0;
				uu = ee[i].u; vv = ee[i].v;
				flag = 0; tot = 0; dfs(rt);
				if(!flag&&tot == n)
				{
					ans = i;break;
				} 
			}	
		}
	}
	else
	{
		for(int i = n; i >= 1; i --)
		{
			for(int j = 1; j <= n; j ++) vis[j] = 0;
			uu = ee[i].u; vv = ee[i].v;
			flag = 0; tot = 0; dfs(ee[i].v);
			if(!flag&&tot == n)
			{
				ans = i;break;
			} 
		}
	}
	printf("%d", ans);
    return 0;
}

3.给大佬递茶

Alice和Bob开始了递茶操作。 一开始Alice和Bob都有一个杯子里面装了N吨的茶。现在每次Alice会等概率地随机向垃圾桶里面倒入4k,3k,2k或者k吨的茶, 并且如果Alice想倒x吨的茶,Bob就会向垃圾桶里面导入4k−x吨的茶。 注意每次操作的时候Alice或者Bob的茶有可能不够多,这个时候就能倒多少到多少。 现在问Alice在四种操作完全等概率的情况下,Alice先把自己的茶倒光的概率加上Alice和Bob同时把茶倒光的概率的一半是多少。 注意,Alice和Bob每轮倒茶都是同时开始同时结束的。

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
const int N = 1e3 + 2;
inline int read()
{
	int x = 0 , f = 1;	char ch = getchar();
	while(ch < '0' || ch > '9')	{if(ch == '-')	f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
	return x * f;
}
int n , m;
double f[N][N];
double ans;
int main()
{
//	freopen("tea.in","r",stdin);
//	freopen("tea.out","w",stdout);
	n = read(); m = read(); n = (int)(ceil((double)n / (double)m)); m = 1;
	if(n >= 300) 
	{
		puts("1.000000");
		fclose(stdin);
		fclose(stdout);
		return 0;
	}
	f[n][n] = 1.0;
	for(int i = n;i >= 1;i --)
	for(int j = n;j >= 1;j --) 
	for(int k = 1;k <= 4;k ++) 
	f[i - k * m < 0 ? 0 : i - k * m][j - (4 - k) * m < 0 ? 0 : j - (4 - k) * m] += f[i][j] * 0.25;
	for(int i = 1;i <= n;i ++) ans += f[0][i];
	 ans += f[0][0] / 2.0;
	printf("%.6f\n",ans);
	fclose(stdin);
	fclose(stdout);
	return 0;
}

qsing4

1.裂变链接

现在有N个异构体在前线排成一排参与战斗,但由于地方火力过于凶猛,异构体们都受到了不同程度的损伤。为了能够让所有异构体继续战斗,避免由于能量不均衡导致的爆炸,异构体们需要使得他们彼此之间的能量值一样。记第i个异构体当前ei的能量,每个时刻,每个异构体都可以做如下三种操作中的一种:

  1. 传递1的能量给自己左边相邻的异构体(如果存在)。
  2. 传递1的能量给自己右边相邻的异构体(如果存在)。
  3. 传递1的能量给自己(摸鱼)。

为了尽快的回到前线作战,异构体们希望在最短的时间内使得所有异构体的能量值一样,问最短时间。数据保证有解。操作过程中自己的能量可以变为负数。

思路:这道题能量可以同时传递,对于每个点,他的左边和右边可能通过它进行能量交换,所以这个点做的贡献

其实就是它左右差的绝对值的差。

代码:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define int long long
using namespace std;
const int maxn=100010;
int n,a[maxn],sum[maxn],ans,yilnr;
signed main()
{
//	freopen("link.in","r",stdin);
//	freopen("link.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<=n;i++) sum[i] = sum[i-1]+a[i];
	yilnr=sum[n]/n;
    for(int i=1;i<=n;i++)
    {
        int ansl=sum[i-1]-(i-1)*yilnr;
        int ansr=sum[n]-sum[i]-(n-i)*yilnr;
        if(ansl < 0 && ansr < 0)
		ans=max(ans,-ansl-ansr);
        ans=max(ans,max(abs(ansl),abs(ansr)));
    }
    printf("%lld\n",ans);
    return 0;
}
/*
3
1 0 5
*/

2.死亡鸽者

死亡鸽者最喜欢的事情就是咕咕咕, 他的座右铭“风萧萧兮易水寒, 壮士一 去兮不复返, 然鸽子至今未到” 也每天被他所忘记。 今天, 死亡鸽者又要开始咕咕的旅行了。 现在有N座城市排成一排, 死亡鸽者会从第一座城市一直走到最后一座城市。 每个城市都有一个数ai, 每次死亡鸽者可以选择取走或者不取走这个数, 但想要取走这个数的话要求这个数必须是所有已经取走的数的倍数或者约数。

现在问死亡鸽者从第一座城市走到最后一座城市的过程中, 最多取走多少个数。

思路:这是个DP,我们可以有显然得,答案的序列肯定是由序列中最小值的某些倍组成,而且打乱顺序

答案不变,我们可以在值域上DP,设\(f[i]\)为序列的最后一个为i的答案的最大值

\(f[j]=f[i]+cnt[j]\)

代码:

#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
const int maxn=1000010;
int n,cnt[maxn],f[maxn],v;
int main()
{
	freopen("pigeon.in","r",stdin);
	freopen("pigeon.out","w",stdout);
    scanf("%d",&n);
    for (int a=1;a<=n;a++)
    {
        scanf("%d",&v);
        cnt[v]++; f[v]++;
    }
    int ans=0;
    for(int i=1;i<=1000000;i++)
    if(f[i])
	{
        if(f[i]>ans) ans=f[i];
        
        for(int j=i+i; j<=1000000;j+=i)
        
        if (cnt[j] && f[i]+cnt[j]>f[j])f[j]=f[i]+cnt[j];
    }
    printf("%d\n",ans);
    return 0;
}

3.进阶之灾

假设我们总共有N层, 在第i层的时候, 我们可以从两张卡牌中选择一张加入我们的卡组, 这两张卡牌的战斗力分别为\(a_i\),\(b_i\)。在经过N层的选择之后, 我们便会有一套N张卡的卡组, 而整套卡组的战斗力取决于卡牌与卡牌之间战斗力差值的绝对值的最小值。 但是心脏是一个非常强大的敌人, 如果我们不能拥有强大的战斗力, 人类就会一败涂地。 所以, 现在我们想知道, 战斗力最大可能是多少。

思路:2-SAT,tarjan,线段树优化建图

代码:

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int N=40010;
struct Edge{int from,to,next;} e[N<<5];
int h[N<<2],sum=0;
int low[N<<2],pre[N<<2];
int scc[N<<2],dfn,cnt;
int a[2][N],n;
pii Hash[N<<1];
stack<int> stk;
int node[N<<4],tot;
void add_edge(int u,int v)
{
    if(u==v) return;
    e[++sum].to=v;
    e[sum].from=u;
    e[sum].next=h[u];
    h[u]=sum;
}

void Build(int o,int l,int r)
{
    if(l==r)
    {
        node[o]=Hash[l].second;
        return;
    }
    node[o]=++tot;
    int mid=(l+r)/2;
    Build(o<<1,l,mid);
    Build(o<<1|1,mid+1,r);
    add_edge(node[o],node[o<<1]);
    add_edge(node[o],node[o<<1|1]);
}

void link(int o,int l,int r,int nl,int nr,int u)
{
    if(nl>nr) return;
    if(l>=nl&&r<=nr)
    {
        add_edge(u,node[o]);
        return;
    }
    int mid=(l+r)/2;
    if(nl<=mid) link(o<<1,l,mid,nl,nr,u);
    if(nr>mid) link(o<<1|1,mid+1,r,nl,nr,u);
}

void Init()
{
    for(int i=1;i<=n;i++)
    {
        Hash[2*i-1].first=a[0][i];
        Hash[2*i-1].second=i+n;
        Hash[2*i].first=a[1][i];
        Hash[2*i].second=i;
    }
    sort(Hash+1,Hash+1+2*n);
}

void Tarjan(int u)
{
    stk.push(u);
    low[u]=pre[u]=++dfn;
    for(int tmp=h[u];tmp;tmp=e[tmp].next)
        if(!pre[e[tmp].to])
        {
            Tarjan(e[tmp].to);
            low[u]=min(low[u],low[e[tmp].to]);
        }
        else if(!scc[e[tmp].to]) low[u]=min(low[u],pre[e[tmp].to]);
    if(pre[u]==low[u])
    {
        int o;cnt++;
        do{
            o=stk.top();
            stk.pop();
            scc[o]=cnt;
        }while(o!=u);
    }
}

bool check(int x)
{
    sum=dfn=cnt=0;tot=2*n;
    memset(h,0,sizeof(h));
    Build(1,1,2*n);
    for(int i=1;i<=n;i++)
        for(int k=0;k<2;k++)
        {
            int l=lower_bound(Hash+1,Hash+1+2*n,pii(a[k][i]-x+1,0))-Hash;
            int r=upper_bound(Hash+1,Hash+1+2*n,pii(a[k][i]+x-1,2*n+1))-Hash-1;
            int m=lower_bound(Hash+1,Hash+1+2*n,pii(a[k][i],(k^1)*n+i))-Hash;
            link(1,1,2*n,l,m-1,k*n+i);
            link(1,1,2*n,m+1,r,k*n+i);
        }
    while(!stk.empty()) stk.pop();
    memset(pre,0,sizeof(pre));
    memset(low,0,sizeof(low));
    memset(scc,0,sizeof(scc));
    for(int i=1;i<=tot;i++)
        if(!pre[i]) Tarjan(i);
    for(int i=1;i<=n;i++)
        if(scc[i]==scc[i+n]) return 0;
    return 1;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&a[0][i],&a[1][i]);
    Init();
    int l=0,r=int(1e9),mid;
    while(l<r)
    {
        mid=(l+r)/2;
        if(check(mid)) l=mid+1;
        else r=mid-1;
    }
    printf("%d\n",check(l)?l:l-1);
    return 0;

qsing5

1.最小差异矩阵

有一个nm的矩阵,矩阵的每个位置上可以放置一个数。对于第i行,第i行的差异定义为该行的最大数和最小数的差。一个矩阵的差异,定义为矩阵中每一行差异的最大值。现在给定k个数v[1..k],问:从这k个数中选nm个数放入矩阵,能够得到的矩阵的差异最小值是多少。

思路:先排序,再二分就行,每次找一个m长度的

代码:

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 100005
using namespace std;
int k,n,m,a[N],l,r;
int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
bool check(int d)
{
	int tmp=0;
	for(int i=1;i<=k-m+1;i++)
	{
		if(a[i+m-1]-a[i]<=d)
		i+=m-1,tmp++;
	}
	if(tmp>=n) return 1;
	return 0;
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	k=read();n=read();m=read();
	for(int i=1;i<=k;i++)a[i]=read();
	sort(a+1,a+k+1);
	l=0; r=1e9;
	while(l<r)
	{
		int mid=(l+r)>>1;
		if(check(mid))r=mid;
		else l=mid+1;
	}
	printf("%d\n",l);
	fclose(stdin);fclose(stdout);
	return 0;
}
/*
5 2 2
7 5 8 2 3
*/

2.分割序列

给定一个长度为的序列,现在要将这个序列分成k段(每段都不能为空),定义每一段的权值为该段上的所有数的或和。定义整个序列的权值为每段权值的和。问:这个序列的最大权值为多少。

思路:DP,加优化,一会发现,某一段的或和是相等的,可以记录一下一段区间的右端点,每次跳右端点即可

而且你会发现\(dp[k][i]\)在i确定的情况下,k越靠右越优,所以,每次跳右端点不会影响答案的最优性

代码:

#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;
#define N 2100
int sum[N][N],dp[N][N],n,k,a[N],nxt[N][N];
int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int main()
{
	freopen("b.in","r",stdin);
	freopen("b.out","w",stdout);
	n=read();k=read();
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1;i<=n;i++) 
	for(int j=i;j<=n;j++)sum[i][j]=sum[i][j-1 ] | a[j];
	for(int i=1;i<=n;i++)
	{
		nxt[i][i]=i;
		for(int j=i-1;j>=1;j--)
		{
			if(sum[j][i]==sum[j+1][i])nxt[j][i]=nxt[j+1][i];
			else nxt[j][i]=j;
		}
	}
	for(int i=1;i<=n;i++)
	for(int j=1;j<=k;j++)
	for(int z=1;z<=i;z++)
	{
		z=nxt[z][i];
		dp[i][j]=max(dp[i][j],dp[z-1][j-1]+sum[z][i]);
	}
	int ans=0;
	for(int i=1;i<=n;i++)ans=max(ans,dp[i][k]);
	printf("%d\n",ans);
	return 0;
}
posted @ 2019-10-13 21:51  yelir  阅读(612)  评论(0编辑  收藏  举报