9.23 a.m.小结

T1:问题 A: 零件分组

题目描述

某工厂生产一批棍状零件,每个零件都有一定的长度(Li)和重量(Wi)。现在为了加工需要,要将它们分成若干组,使每一组的零件都能排成一个长度和重量都不下降(若 i<j,则 Li<=Lj,Wi<=Wj)的序列。请问至少要分成几组?

输入

第一行为一个整数 N(N<=1000),表示零件的个数。第二行有 N 对正整数,每对正整数表示这些零件的长度和重量,长度和重量均不超过 10000。

输出

仅一行,即最少分成的组数。

样例输入

5

8 4 3 8 2 3 9 7 3 5

样例输出

2

题解

这道题实际上就是考拦截导弹的思想。首先按照长度升序排序(长度相同按照重量升序排序),然后对于每一个重量,看一下能不能入组,能入组就入,否则新开一组。最后的答案就是组数。

参考代码

#include<cstdio>
#include<algorithm>
using namespace std;
struct node { int l,r; }a[1010];
node b[1010];int vis[1010];
bool comp1(node p,node q)
{ return p.l<q.l||p.l==q.l&&p.r<q.r; }
bool comp2(node p,node q)
{ return p.r<q.r; }
int n,ans=0,tot=0,vis2[1010];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d%d",&a[i].l,&a[i].r);
	sort(a+1,a+n+1,comp1);
	int pd=0;ans=1;
	vis[++tot]=a[1].r;
	for(int i=2;i<=n;i++)
	{
		int rs=0;
		for(int j=1;j<=tot;j++)
		{
			if(a[i].r<vis[j]) continue; 
			if(a[i].r==vis[j])
			{ rs=j;break; }
			if(vis[j]>vis[rs]) rs=j;
		}
		if(rs==0) vis[++tot]=a[i].r;
		else vis[rs]=a[i].r;
	}
	printf("%d",tot);
	return 0;
}

T2:问题 B: 似乎在梦中见过的样子

题目描述

    “Madoka,不要相信 QB!”伴随着 Homura 的失望地喊叫,Madoka 与 QB 签订了契约。
    这是 Modoka 的一个噩梦,也同时是上个轮回中所发生的事。为了使这一次 Madoka 不再与 QB 签订契约,Homura 决定在刚到学校的第一天就解决 QB。然而,QB 也是有许多替身的(但在第八话中的剧情显示它也有可能是无限重生的),不过,意志坚定的 Homura 是不会放弃的——她决定消灭所有可能是 QB 的东西。现在,她已感受到附近的状态,并且把它转化为一个长度为 n 的字符串交给了学 OI 的你。
    现在你从她的话中知道,所有形似于 A+B+A 的字串都是 QB 或它的替身,且 len(A)≥k,len(B)≥1(位置不同其他性质相同的子串算不同子串,位置相同但拆分不同的子串算同一子串),然后你必须尽快告诉 Homura 这个答案——QB 以及它的替身的数量。

输入

第一行一个字符串 S,第二行一个数 k。

输出

仅一行一个数,表示 QB 以及它的替身的数量。

样例输入

aaaaa

1

样例输出

6

提示


【样例输入2】

abcabcabc

2

【样例输出2】

8


【数据规模】


对于100%的数据,n≤15000,k≤100,且字符集为所有小写字母。

题解

(这道题刚开始做的才是真的难受,编号从0开始就对,从1开始就错,最后搞了好久才发现是i-1和j搞错了……)观察这道题,找A+B+A,发现就是找到一个A,然后找之前的一个A,且两个A不紧靠不重叠。这不就是KMP??Nxt函数的意义就是找到一个最长的非前缀与前缀相同。因此朴素想法,枚举左端点,判断右端点是否符合条件,符合条件的依据就是nxt值是否大于等于题目中要求的k以及是否重复(代码中有2个kmp,分开处理不容易错,参见第二个kmp)。位置相同拆分不同算同一子串相当于给了这种kmp算法一个得天独厚的条件。

参考代码

#include<cstdio>
#include<cstring>
using namespace std;
char a[20000];int k,lens,ans=0,nxt[20000];
void kmp(int st)
{
	st--;
	nxt[st+1]=st;
	for(int i=st+2,j=st;i<=lens;i++)
	{
		while(j>st&&a[i]!=a[j+1]) j=nxt[j];
		if(a[i]==a[j+1]) j++;
		nxt[i]=j;
	}
	for(int i=st+2,j=st;i<=lens;i++)
	{
		while(j>st&&a[i]!=a[j+1]) j=nxt[j];
		if(a[i]==a[j+1]) j++;
		while((j-st)*2>=i-st) j=nxt[j];
		if(j-st>=k) ans++;
	}
}
int main()
{
	scanf("%s",a);scanf("%d",&k);
	lens=strlen(a);
	for(int i=1;i+k*2-1<=lens;i++) kmp(i-1);
	printf("%d",ans);
	return 0;
} 

T3:问题 C: 最优贸易

题目描述

C 国有n 个大城市和m 条道路,每条道路连接这n 个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为1 条。
C 国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。
商人阿龙来到 C 国旅游。当他得知同一种商品在不同城市的价格可能会不同这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚回一点旅费。设C 国n 个城市的标号从1~ n,阿龙决定从1 号城市出发,并最终在n 号城市结束自己的旅行。在旅游的过程中,任何城市可以重复经过多次,但不要求经过所有n 个城市。阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品——水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。由于阿龙主要是来C 国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。
假设 C 国有5 个大城市,城市的编号和道路连接情况如下图,单向箭头表示这条道路为单向通行,双向箭头表示这条道路为双向通行。


假设 1~n 号城市的水晶球价格分别为4,3,5,6,1。阿龙可以选择如下一条线路:1->2->3->5,并在2 号城市以3 的价格买入水晶球,在3号城市以5 的价格卖出水晶球,赚取的旅费数为2。
阿龙也可以选择如下一条线路 1->4->5->4->5,并在第1 次到达5 号城市时以1 的价格买入水晶球,在第2 次到达4 号城市时以6 的价格卖出水晶球,赚取的旅费数为5。
现在给出 n 个城市的水晶球价格,m 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。请你告诉阿龙,他最多能赚取多少旅费。

 

输入

第一行包含 2 个正整数n 和m,中间用一个空格隔开,分别表示城市的数目和道路的数目。
第二行 n 个正整数,每两个整数之间用一个空格隔开,按标号顺序分别表示这n 个城市的商品价格。
接下来 m 行,每行有3 个正整数,x,y,z,每两个整数之间用一个空格隔开。如果z=1,表示这条道路是城市x 到城市y 之间的单向道路;如果z=2,表示这条道路为城市x 和城市y 之间的双向道路。

输出

共1 行,包含1 个整数,表示最多能赚取的旅费。如果没有进行贸易,则输出0。

样例输入

5 5

4 3 5 6 1

1 2 1

1 4 1

2 3 2

3 5 1

4 5 2

样例输出

5

提示

【数据规模】

输入数据保证 1 号城市可以到达n 号城市。

对于 10%的数据,1≤n≤6。

对于 30%的数据,1≤n≤100。

对于 50%的数据,不存在一条旅游路线,可以从一个城市出发,再回到这个城市。

对于 100%的数据,1≤n≤100000,1≤m≤500000,1≤x,y≤n,1≤z≤2,1≤各城市水晶球价格≤100。

题解

首先明确两个数组,L和R,分别表示经过当前城市的最小价格以及在通向终点的路上的最大的价格,两个相减就是经过当前城市的最大差价。因此做法也很明确:建立2个图(一正一反),然后跑2个SPFA算法(一个求min,所以要赋极大值;一个求max,所以要赋极小值)分别跑出L和R数组,最后O(n)枚举就能够完成本题。(注意代码中的d,l数组就代表L,R数组)

参考代码

#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
struct tree { int nxt,to; };
int max1(int p,int q) { return p>q?p:q; }
int min1(int p,int q) { return p<q?p:q; }
queue<int>q;int ans=-999999999;
tree tr1[1001000],tr2[1001000];
int n,m,head1[1001000],head2[1001000],cnt1=0,cnt2=0;
int a[1001000],d[1001000],l[1001000],vis1[1001000],vis2[1001000];
void build_tree(int u,int v)
{
	tr1[++cnt1].nxt=head1[u];
	tr1[cnt1].to=v;
	head1[u]=cnt1;
	tr2[++cnt2].nxt=head2[v];
	tr2[cnt2].to=u;
	head2[v]=cnt2;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=m;i++) 
	{
		int u,v;scanf("%d%d",&u,&v);
		int pd;scanf("%d",&pd);
		build_tree(u,v);
		if(pd==2) build_tree(v,u);
	}
	q.push(1);vis1[1]=1;
	for(int i=1;i<=n;i++)
	{
		d[i]=707406378;
		l[i]=-707406378;
	}
	while(!q.empty())
	{
		int pt=q.front();q.pop();
		vis1[pt]=0;
		for(int i=head1[pt];i;i=tr1[i].nxt)
		{
			int to=tr1[i].to;
			if(d[to]>min1(d[pt],a[to]))
			{
				d[to]=min1(d[pt],a[to]);
				if(!vis1[to])
				{
					vis1[to]=1;
					q.push(to);
				}
			}
		}
	}
	q.push(n);vis2[n]=1;
	while(!q.empty())
	{
		int pt=q.front();q.pop();
		vis2[pt]=0;
		for(int i=head2[pt];i;i=tr2[i].nxt)
		{
			int to=tr2[i].to;
			if(l[to]<max1(l[pt],a[to]))
			{
				l[to]=max1(l[pt],a[to]);
				if(!vis2[to])
				{
					vis2[to]=1;
					q.push(to);
				}
			}
		}
	}
	for(int i=1;i<=n;i++)
		if(d[i]!=707406378&&l[i]!=-707406378)
			ans=max1(ans,l[i]-d[i]);
	printf("%d",ans);
	return 0;
}

T4:问题 D: Radio Transmission

题目描述

给你一个字符串,它是由某个字符串不断自我连接形成的。但是这个字符串是不确定的,现在只想知道它的最短长度是多少。

输入

第一行给出字符串的长度 L,第二行给出一个字符串,全由小写字母组成。

输出

一行一个整数,表示最短的长度。

样例输入

8

cabcabca

样例输出

3

提示

【样例说明】

对于样例,我们可以利用 abc 不断自我连接得到 abcabcabc,读入的 cabcabca 是它的子串。

【数据范围】

对于全部数据,1≤L≤106 。

题解

有位大佬说得好,这不是解答题,而是一道证明题,证明答案就是n-next[n]。

无论如何,next是求最长非前缀与前缀匹配的最大长度,可以想象减了之后是剩下了什么?由于这是由某一字符串自我连接并且取子串,因此可以找到一个最大字符串被这个子串包含。想一想平移的效果。对于末端点,往前移最快能移到上一段“重复子串”的末尾,或者是看首端点,用一个next也只会移到后面那个(第二个)循环子串的那个相同位置的点。因此,最好画图理解……

参考代码

#include<cstdio>
using namespace std;
char s[520000];
int lens,nxt[520000];
int main()
{
	scanf("%d",&lens);
	scanf("%s",s+1);
	nxt[1]=0;
	for(int i=2,j=0;i<=lens;i++)
	{
		while(j>0&&s[i]!=s[j+1]) j=nxt[j];
		if(s[i]==s[j+1]) j++;
		nxt[i]=j;
	}
	printf("%d",lens-nxt[lens]);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值