hiho一下合集

目录

hiho 251 歌德巴赫猜想

hiho 236 水陆距离

hiho 235 闰秒

hiho 234 矩形计数

hiho 233 数组分拆

hiho 232 拆字游戏

hiho 231 小Ho的强迫症

hiho 230 Smallest Substring

hiho 229 Same Letters In A Row

hiho 228 Parentheses Matching

hiho 227 Longest Subsequence

hiho 226 Ctrl-C Ctrl-V

hiho 225 Inside Triangle

hiho 221 Push Button II

hiho 220 Push Button I

hiho 219 Smallest Rectangle

hiho 218 Keywords Filter

hiho 217 Logic Expression Tree

hiho 216 Gas Stations

hiho 215 Circle Detect

hiho 214 Sorting Photo Files

hiho 213 Boarding Passes

hiho 212 Target Sum

hiho 211 Total Hamming Distance

hiho 210  Inventory is Full

hiho 209 Powers of Two

hiho 208 Smallest Sub Array


hiho 259 风格不统一如何写程序

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

小Hi写程序时习惯用蛇形命名法(snake case)为变量起名字,即用下划线将单词连接起来,例如:file_name、 line_number。  

小Ho写程序时习惯用驼峰命名法(camel case)为变量起名字,即第一个单词首字母小写,后面单词首字母大写,例如:fileName、lineNumber。  

为了风格统一,他们决定邀请公正的第三方来编写一个转换程序,可以把一种命名法的变量名转换为另一种命名法的变量名。

你能帮助他们解决这一难题吗?

输入

第一行包含一个整数N,表示测试数据的组数。(1 <= N <= 10)    

以下N行每行包含一个以某种命名法命名的变量名,长度不超过100。

输入保证组成变量名的单词只包含小写字母。

输出

对于每组数据,输出使用另一种命名法时对应的变量名。

样例输入

2  
file_name  
lineNumber 

样例输出

fileName  
line_number

1.题是什么?

    简而言之就是代码规范中的驼峰转换问题,给你n个大小100以内的字符串,每个字符串必然是蛇形命名法或者驼峰命名法之一,要求你听过代码将之转换成另一种命名法。

2.思路

    对目标串做线性遍历,遍历的同时用一个新字符数组temp记录转换后的字符串,遍历结束后输出temp就好,转换方法就是遍历到下划线时在temp中仅加入下划线后的字母的大写形式,遍历到大写字母时temp中加入下划线与此字母的小写形式,其余字符都直接复制到temp中就好。之所以可以这么做是因为输入保证单词由小写字母组成

3.ac代码

#include <stdio.h> 

const int maxn=10+5;
const int maxs=100+5;

void solve(){
	int n;
	scanf("%d",&n);
	char a[maxs],temp[maxs];
	for(int i=0;i<n;i++){
		scanf("%s",&a);
		int pos=0,len=0;
		while(a[pos]){
			if(a[pos]=='_') temp[len++]=a[++pos]-32;//注意这里++pos,后面的pos++也会执行故而总共后移了两位,一个小优化
			else if(a[pos]<='Z'){
				temp[len++]='_';
				temp[len++]=a[pos]+32;
			}
			else temp[len++]=a[pos];
			pos++;
		}
		temp[len]='\0';
		printf("%s\n",temp);
	}
	 
} 

int main(){
	solve();
	return 0;
}

hiho 208 Smallest Sub Array

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

Given an array of integers A, find the smallest contiguous sub array B of A such that when you sort B in ascending order the whole array A becomes sorted as well.

For example if A = [1, 2, 3, 7, 4, 6, 5, 8] the smallest B will be [7, 4, 6, 5].  

输入

The first line contains an integer N denoting the length of A. (1 <= N <= 100000)

The second line contains N integers denoting the array A. (0 <= Ai <= 100000000)

输出

The length of the smalltest sub array B.

样例输入

8  
1 2 3 7 4 6 5 8

样例输出

4

题目分析:

    本题的大意是对一个包含N个整数的数组,找到一个最短的连续子数组,使得只要把子数组的数从小到大排序之后,整个数组就是排好序的了。

    较简单的法子:

    1.直接将原数组A排序,得到排好序的数组B

    2.然后从左到右比较依次A[i]B[i],找到第一个不相同的位置p;再从右到左依次比较找到第一个不相同的位置q

    3.q-p+1即为答案

    优点:思维特别,抓住了特点,可以很快秒掉这道题

    缺点:复杂度为n*logn,虽然题可以ac,不过不完美

    高效的方法:

    核心思维:先找到符合条件的中间那一段再调整两端

    1.从左到右找到符合升序的最后一位的下标 l.

    2.判断l==n-1?,相等代表数组已经是排序完毕的了,故而直接输出0,否则继续

    3.从右到左找到符合升序的最后一位的下标 r.

    4.a数组现在被我分成三段,左边已排序好的一段a1,中间乱的一段a2,右边已排序好的一段a3.

    5.可知a1内元素的最大值就是a[l],a3内元素最小值就是a[r],求出a1与a2内元素的最大值nowmax,a2与a3内元素最小值nowmin.

    6.现在的ans是a2的长度r-l-1

    7.将a3段从左到右依次将小于nowmax的都归入a2,即为将ans++,将a1段从右到左依次将大于nowmin的都归入a2,即为将ans++,

    8.ans即为答案

优点,复杂度为n,更加高效

缺点:总感觉我的解法很繁复,好像多做了什么,不够完美

ac代码:

#include <stdio.h>

int mymax(int a,int b){ return a>b?a:b; } 
int mymin(int a,int b){ return a<b?a:b; } 

int a[100005];
void solve(){
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++) scanf("%d",&a[i]); 
	int l;
	for(l=0;l<n-1;l++) if(a[l]>a[l+1]) break; 
	if(l==n-1) printf("0\n");
	else{
		int r;
		for(r=n-1;r>l;r--) if(a[r-1]>a[r]) break; 
		int nowmax=-1,nowmin=(1<<30);
		for(int i=l;i<r;i++) nowmax=mymax(nowmax,a[i]);
		for(int i=l+1;i<=r;i++)	nowmin=mymin(nowmin,a[i]);
		int ans=r-l-1;
		while(r<n&&a[r++]<nowmax) ans++;
		while(l>=0&&a[l--]>nowmin) ans++;	 
		printf("%d\n",ans);
	}
}

int main(){
	solve();
	return 0;
}

注:一定要小心边界检查;我猜最后一个评测数据是,不检查边界会出事

2
2 1

hiho 209 Powers of Two

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

Given a positive integer N, it is possible to represent N as the sum of several positive or negative powers of 2 (± 2k for some k). For example 7 can be represented as 22 + 21 + 20 and 23 + (-20).

Your task is to find the representation which contains the minimum powers of 2.

输入

One positive integer N.  

For 80% of the data: 1 <= N <= 100000  

For 100% of the data: 1 <= N <= 2000000000

输出

The minimum number of powers of 2.

样例输入

7

样例输出

2

题意分析:

    给你一个数字n,比如7,7可以描述为2^2+2^1+2^0我们是知道的,但我们现在也可以将之描述为2^3-2^1,

前一种方法用了3个描述7这个数字,而后一种只用了两个,我们就是要找到能描述出n的最少数字个数.

思路:

    通过n的二进制码动手,我们可以发觉之所以我们能用更少的2幂次数来描述一个数是因为我们普遍的采用了一种方法:

    比如数字1265的二进制如下

                1  0  0  1  1  1  1  0  0  0  1

                1  0  1  0  0  0 -1  0  0  0  1

    像出现上图红色部分这种连续的1时,我们可以将红色部分转为下方的形式(-1即表示减去这个位置的位权),这样就完成了如下转化:    2^11+2^8+2^7+2^6+2^5+2^0   ->   2^11+2^9-2^5+2^0

    描述所需数字从6个降低到了4个,这就是核心思维.

    实现时的小细节:

    设计为仅位移到0的位置时进行判断是否执行收缩(收缩指的就是上面所介绍的那种缩短方法),为1时只是记录1的数目加1;因为这个设计由0触发,故而对应7的二进制,我们应该理解为0111而不是111;最后的0用于触发收尾.

ac代码:

#include <stdio.h>

int mycal(int n){ 
	int ans,k; 
	ans=k=0; 
	do{
		k+=n&1;   //记录当前位置前方连续的1的数目
		n>>=1;
		if(!(n&1)){ 
			if(n&2){  
				if(k>1){//执行收缩
					ans+=1;
					k=1;
				}
				else if(k==1){ //不收缩
					ans+=1;
					k=0;
				}
			}else{
				if(k>1){//执行收缩
					ans+=2;
					k=0;
				}
				else if(k==1){ //不收缩
					ans+=1;
					k=0;
				}
			}
		}
	}while(n);
	return ans; 
}

int main(){
	int n;
	scanf("%d",&n);
	printf("%d",mycal(n));
	return 0;
}

hiho 210  Inventory is Full

    时间限制:10000ms

    单点时限:1000ms

    内存限制:256MB

描述

You're playing your favorite RPG and your character has just found a room full of treasures.

You have N inventory slots. Luckily, items of the same type stack together, with the maximum size of the stack depending on the type (e.g. coins might stack to 10, diamonds to 5, armor to 1, etc.). Each stack (or partial stack) takes up 1 inventory slot. Each item has a selling value (e.g. a single diamond might be worth 10, so a stack of 5 diamonds would be worth 50). You want to maximize the total selling value of the items in your inventory. Write a program to work it out.  

输入

The first line contains 2 integers N and M denoting the number of inventory slots and the number of unique item types in the room.  

The second line contains M integers, A1, A2, ... AM, denoting the amounts of each type.   
The third line contains M integers, P1, P2, ... PM,  denoting the selling value per item of each type.  
The fourth line contains M integers, S1, S2, ... SM, denoting the maximum stack size of each type.  

For 80% of the data: 1 <= Ai <= 100000  
For 100% of the data: 1 <= N, M <= 10000 1 <= Ai <= 1000000000 1 <= Pi <= 1000000 1 <= Si <= 100  

输出

The maximum total selling value.

样例输入

5 10  
10 10 10 10 10 10 10 10 10 10  
1 2 3 4 5 6 7 8 9 10  
10 9 8 7 6 5 4 3 2 1

样例输出

146 

题意分析:

1.题是什么?

    一道感觉起来很实用的小题目,题意就是现在你面前有m种物品,告诉你第i种物品有ai个,单个价值为bi,背包里的重叠上限为ci(玩过rpg的都知道收集到的资源在背包里是可以叠的,不过单个格子最多只能重叠固定数目个,还是不懂的话看下图),现在的问题就是我的背包只有n个格子我一次怎么装最多价值的东西走.

2.什么是rpg中的重叠上限?

    看下图的背包中第一排的胶带每个格子的重叠上限就是20个,第二排的木板和铁的重叠上限为20,可是第三排的铁屑重叠上限为50,而第三排最后一个铁镐一个物品就占一个格子,铁镐的重叠上限为1,这就是rpg游戏中每个背包格子的重叠上限的概念.

    

   

3.为什么我们选择贪心而不是动态规划

    首先我们把背包里这n个格子该放什么,放多少这个问题转化为:

        第1个格子放什么放多少-> 第2个格子放什么放多少->....... 第n个格子放什么放多少

    这一步问题转化是没问题的,因为背包里东西的存放是不在意顺序的,每个格子也一样.

    然后问题明显就是一个分步骤决策的最优化问题,你肯定想到了背包问题,可是我们明白有时对于简单题贪心也行,而什么时候该贪心什么时候该动态规划呢?

首先我们了解一下什么是贪心法:

    贪心法是每一步都构造当前最优解,以期望最终答案为最优解.

    动态规划是通过组合子问题的最优解从而得到整个问题的最优解.

    理论上所有贪心可以做的题动态规划都能做,而区别的话我后面会出一个博客专门讲贪心与动态规划的区别,现在先看看别人的博客吧,这篇不错:

    未经许可,侵权请告诉我,我立删链接:贪心算法精讲

4.这个问题怎么贪心?

    贪心起来就很方便了,我们算法要做的就是确定每个背包格子该选哪个物品重叠多少个

    这里我们用一个大根堆(这里可以用c++ stl里的优先队列实现),把m个物品放进去,排序规则自定义为根据ai*min(bi,ci),ai是物品价值,bi是物品当前剩余个数,ci是物品重叠上限.然后我们拿n次,每次拿堆顶的那种物品,数目当然是尽可能的多拿,其实就是min(bi,ci),如果物品没被拿完就更新剩余个数然后重新压回堆中,拿完了就不压回去了.

    就这样子,n个格子放什么放多少自然就出来了,答案要问的总价值在算的过程中就可以叠加出来了.

ac代码:

#include <iostream>
#include <stdio.h>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef pair<int,int> paii;

const int inf=(1<<30);
const int maxm=10005;

struct item{
	ll amount;
	ll price;
	ll maxsize;
}items[maxm];
bool operator<(const item &a,const item &b){
    return a.price*std::min(a.maxsize,a.amount)<b.price*std::min(b.maxsize,b.amount);
}

void solve(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=0;i<m;i++) scanf("%lld",&items[i].amount);
	for(int i=0;i<m;i++) scanf("%lld",&items[i].price);
	for(int i=0;i<m;i++) scanf("%lld",&items[i].maxsize);
	priority_queue<item> que;
	for(int i=0;i<m;i++) que.push(items[i]);
	ll ans=0;
	while(!que.empty()&&n){
		item t=que.top();
		que.pop();
		if(t.amount>t.maxsize){
			ans+=t.maxsize*t.price;
			t.amount=t.amount-t.maxsize;
			que.push(t);
		}
		else ans+=t.amount*t.price;
		n--;
	}
	printf("%lld\n",ans);
}


int main(){
	solve();
	return 0;
}

hiho 211 Total Hamming Distance

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

The Hamming Distance between two integers is the number of different digits between their binary representation. For example the hamming distance between 4(=01002) and 14(11102) is 2.

Given N integers, A1, A2, ... AN, find out the total hamming distance of all pairs of them.  

输入

The first line contains an integer N.  

The second line contains N integers denoting A1, A2, ... AN.

For 80% of the data: 1 <= N <= 1000

For 100% of the data: 1 <= N <= 100000 1 <= Ai <= 1000000000

输出

The total hamming distance

样例输入

3  
1 2 3

样例输出

4

题意分析:

    其实就是这么一道题,我们现在定义两个数字a,b之间的Hamming Distance值为两个数字的二进制表示中不同的位的个数,换句话说其实就是a异或b的结果的二进制表示中1的个数。问题就是给你n个数,让你回答你这n个数的所有组合的Hamming Distance值之和为多少?

    这道的朴素解法其实就是双重循环列举所有组合,对每个组合a,b求a异或b然后计算二进制中1的数目,然后加到总答案上。

可是由于复杂度为n*n故而只能通过百分之八十的数据,先放这个简单思路,再放我ac了的复杂度n的算法:

//只能过百分之八十,AC的在后面
#include <stdio.h>
typedef long long ll; 

const int maxn=100005;
int a[maxn];

int hamdis(int a,int b){ //返回a,b的Hamming Distance值 
	int t=a^b,ans=0;
	while(t){
		ans++;
		t-=t&-t; //精巧的小设计 
	}
	return ans;
}

void solve(){
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++) scanf("%d",&a[i]);
	ll sum=0;
	for(int i=0;i<n;i++) for(int j=i+1;j<n;j++) sum+=hamdis(a[i],a[j]); //枚举所有组合并叠加 
	printf("%lld",sum);
}

int main(){
	solve();
	return 0;
}

    我们在思考优化的时候一般思维都是去思考原来的做法多做了什么事以至于复杂度高,然而这道题我们很难这么做,因为这道题的高效做法是思维换血了,其实完全可以预处理一下n个数字,处理出每个位上1的数目num1和0的数目num0,然后这n个数字在这个位上产生的Hamming Distance值其实就是num1*num0.改变了求得答案的方法。

ac代码

#include <stdio.h>
typedef long long ll; 

const int maxn=100005;
int a[maxn];
ll num[31][2];//num[i][0]存的是这n个数字的二进制表示中中第i个位为0的数字个数,num[i][1]存的是这n个数字的二进制表示中中第i个位为1的数字个数,

void solve(){
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++) scanf("%d",&a[i]);
	for(int i=0;i<31;i++) for(int j=0;j<2;j++) num[i][j]=0; //初始化 
	for(int i=0;i<n;i++){
		for(int j=0;j<31;j++){
			if(a[i]&(1<<j)) num[j][1]++;//位运算判断第i个数字第j位是否为1 
			else num[j][0]++;
	 	}
	}
	ll sum=0;
	for(int i=0;i<31;i++) sum+=num[i][0]*num[i][1]; // num[i][0]*num[i][1]不就是第i位上能使Hamming Distance值+1的所有组合数吗 
	printf("%lld",sum);
}


int main(){
	solve();
	return 0;
}

看完讨论之后修改出了更优秀的解法:

#include <stdio.h>
typedef long long ll; 

int a[100005];
int main(){
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++) scanf("%d",&a[i]);
	ll sum=0,t;
	for(int i=0;i<31;i++){
		t=0;
		for(int j=0;j<n;j++) if(a[j]&(1<<i)) t++;
		sum+=t*(n-t);
	}
	printf("%lld",sum);
	return 0;
}

其实就是交换了一下两个循环,就可以边处理边叠加答案了,之前是我自己思维不够精妙。

hiho 212 Target Sum

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

There is a sequence of N positive integers A1, A2, ... AN and a target sum S. You need to add a plus or minus operator (+/-) before every integer so that the resulted expression equals to S. Find out the number of such expressions.

Example if the sequence A is [1, 1, 1, 1, 1] and the target sum S is 3. There are 5 valid expressions: -1+1+1+1+1=3, +1-1+1+1+1=3, +1+1-1+1+1=3, +1+1+1-1+1=3 and +1+1+1+1-1=3.

输入

The first line contains 2 integers N and S denoting the number of integers and the target sum.  

The second line contains N integers A1, A2, ... AN.

For 80% of the data: 1 <= N <= 10

For 100% of the data: 1 <= N <= 100 1 <= S <= 100000 1 <= Ai <= 1000

输出

The number of valid expressions. The answer may be very large so you only need to output the answer modulo 1000000007.

样例输入

5 3
1 1 1 1 1

样例输出

5

题意分析:

1.题是什么?

    给你n个数字的排列,每个数字前面都必须添加一个加号或者减号,添加好之后把它看作一个算式计算值,问你值为sum的符号填写方案有几种?(取模1000000007)

2.思路

(1).普遍思路

    像这种方案的一看就应该反应过来是DP,普通思路的话就是一个线性的DP,具体看这儿就好 hiho212讨论

(2).我的思路

    我自己第一时间也是想到的普通思路,不过因为前方的求和结果可能为负数,故而DP时直接做值到下标的映射挺麻烦的,还必须用滚动数组,另外复杂度方面也会被固定为n*值域大小,偷懒的我选择去尝试简化问题:

    问题其实就是让你做决策哪些位置该放加号哪些位置该放减号,然后由于每个位置都必须放而且只能是加减号,故而我假设一开始每个位置都是加号,我要做的只是判断我该把哪些变为减号使之和满足条件,有多少种.

    我们首先得到一个重要的数值gap:每个符号都是+时也就是所有数字的和与目标sum的差值

    其实原本不满足变为满足的过程就是将某些加号变减号使gap逐渐缩小直至0的过程.而位置i前面的符号由+变为-对gap的影响就是-2*a[i],故而问题被我们转化成从n个数字中选取某些数字,这些数字的二倍的和等于gap则此为一个满足条件的情况,说白了就是要找我需要选哪些数字才能填平gap,填平了那肯定这是满足条件的.

    问题变成了这样子,滚动数组什么的都不需要了,其实就是n个数字中选部分数字使之和为gap的组数,记住对数字取个二倍.

(3).小细节

    gap一定要先判断一下是不是小于0的,否则会bug.

    虽然不需要滚动数组,但一定要记住递推时要for(int j=gap;j>=2*a[i];j--),这样才能正确的覆盖进行DP.

ac代码:

#include <stdio.h>
typedef long long ll;
const int maxn=100005;
const ll mod=1000000007;
int a[100]; 
ll dp[maxn]; //dp[i]表示当前和为i的结果种数 

int main(){
	int n,sum;
	scanf("%d%d",&n,&sum);
	for(int i=0;i<n;i++) scanf("%d",&a[i]);
	
	int gap=-sum; //gap为n个数的和减去sum
	for(int i=0;i<n;i++) gap+=a[i];
	
	if(gap<0) printf("0");
	else{
		for(int i=0;i<maxn;i++) dp[i]=0;
		dp[0]=1; //初始只有和为0这么一个情况,堆空间初始化好就全是0,可是安全起见还是初始化一下 
		for(int i=0;i<n;i++) for(int j=gap;j>=2*a[i];j--) dp[j]=(dp[j]+dp[j-2*a[i]])%mod; //核心递推,其实就是求解从n个数字中取和为gap的组合数 
	    printf("%lld",dp[gap]);
	}                                          
	return 0; 
}

hiho 213 Boarding Passes

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

Long long ago you took a crazy trip around the world. You can not remember which cities did you start and finish the trip.  Luckily you have all the boarding passes. Can you find out the starting city and ending city?

输入

The first line contains a number N denoting the number of boarding passes.  (1 <= N <= 100)

The following N lines each contain a pair of cities (the city name consists of no more than 10 capital letters)

It is guaranteed that there is always a valid solution.

输出

The starting and ending cities separated by a space.

样例输入

4  
SFO HKG  
PVG BOS  
HKG ABC  
ABC PVG

样例输出

SFO BOS

题意分析:

1.题是什么?

    你以前旅行过很多地方不过你只记得路径忘了起点与终点.现在告诉你n对路径ai,bi,每对表示你曾经从ai出发去到bi,现在要你输出旅行的起点与终点,输入保证一定有答案.

2.思路

   首先我们一定得想到题上并没有说只会经过某个地方一次,故而可能经过同一个地方多次,而在图论中起点的出度一定比入度多1,终点的入度一定比入度多1.故而我们要做的就是对这n对数据做一个map映射记录每个地点的 入度-出度 为多少.

ac代码

#include <iostream>
#include <string>
#include <map>

using namespace std;

int main(){
	int n;
	cin>>n;
	string t1,t2;
	map<string,int> m;
	map<string,int>::iterator ite;
	for(int i=0;i<n;i++){
		cin>>t1>>t2;
		m[t1]--;
		m[t2]++;
	}
	string start,end;
	for(ite=m.begin();ite!=m.end();ite++){
		if(ite->second==-1) start=ite->first;
		else if(ite->second==1) end=ite->first;
	}
	cout<<start<<" "<<end<<endl;
	return 0;
}

hiho 214 Sorting Photo Files

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

You have a lot of photos whose file names are like:

beijing1  

beijing10

beijing8

shanghai233

As you can see, all names have exactly two parts: the string part and the number part. If you sort the files in lexicographical order "beijing10" will go before "beijing8". You do not like that. You want to sort the files first in lexicographical order of the string part then in ascending order of the number part. Can you write a program to work it out?  

输入

The first line contains an integer N denoting the number of files. (1 <= N <= 100)  

The following N lines each contain a file name as described above.  

It is guaranteed that the number part has no leading zeroes and is no more than 1000000.

输出

The sorted files one per line.

样例输入

4  
beijing1  
beijing10 
beijing8 
b233

样例输出

b233  
beijing1  
beijing8  
beijing10

题目简介:

1.题是什么?

    给你最多100个字符串,每个字符串的意义是一个地点加上一个数,现在我要你将这些字符串在根据地点做字典序升序排列的前提下再根据数的大小做一个升序,输出顺序.

2.思路

    明显就是在原本的纯字典序的情况下另外加了点判断,因为C++stl中sort可以支持对string的字典序快排,故而我们只需要重载一下sort的比较函数使比较规则符合题意,然后sort一下就输出即可.

3.小细节

    注意按纯字典序升序beijing10会排在beijing8前面,而我们的新规则明显地点beijing一样,10比8大故而beijing10应在beijing8后面.

ac代码:

#include <iostream>
#include <algorithm>
using namespace std;

bool comp(string a,string b){
	int ta=0,tb=0;
	while(1){
		if(a[ta]<='9'&&b[tb]<='9') break;
		if(a[ta]<b[tb]) return true;
		if(a[ta]>b[tb]) return false;
		ta++,tb++;
	}
	int numa=0,numb=0;
	while(a[ta]) numa=numa*10+a[ta++]-'0';
	while(b[tb]) numb=numb*10+b[tb++]-'0';
	if(numa<numb) return true;
	return false;
}


int main(){
	int n;
	cin>>n;
	string a[100];
	for(int i=0;i<n;i++) cin>>a[i];
	sort(a,a+n,comp);
	for(int i=0;i<n;i++) cout<<a[i]<<endl;
	return 0;
}

hiho 215 Circle Detect

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

You are given a directed graph G which has N nodes and M directed edges. Your task is to detect whether it contains any circle.  

输入

The first line contains an integer T denoting the number of test cases. (1 <= T <= 5)  

For each test case the first line contains two integers N and M. (1 <= N, M <= 100000)  

Then follows M lines. Each contains two integers u and v denoting there is an edge from u to v. (1 <= u, v <= N)

输出

For each test case output "YES" or "NO" denoting whether there is a circle in the graph.

样例输入

2
5 5  
1 2  
2 3  
4 5  
5 4  
4 2
3 2
1 2
2 3

样例输出

YES  
NO 

题意分析:

1.题是什么?

    这道题很直白,就是给你一个有向图,让你判断是否存在环.

2.思路

    由于是稀疏图,邻接表存图,通过拓扑排序判环,如果拓扑可以经过所有的点则不存在环

ac代码:

//基于邻接表的拓扑排序 
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
const int maxn=1e5+5;

int rudu[maxn];
vector<int> vec[maxn];

int main(){
	int t;
	cin>>t;
	while(t--){
		int n,m;
		cin>>n>>m;
		
		for(int i=0;i<n+1;i++){
			vec[i].clear();
			rudu[i]=0;
		}
		while(m--){
			int u,v;
			cin>>u>>v;
			vec[u].push_back(v);
			rudu[v]++;
		}
		
		int count=0;
		queue<int> q;
		for(int i=1;i<=n;i++) if(rudu[i]==0) q.push(i);
		 
		while(!q.empty()){
			int t=q.front();
			q.pop();
			count++;
			for(int i=0;i<vec[t].size();i++) if(--rudu[vec[t][i]]==0) q.push(vec[t][i]);
		}
		
		if(count!=n) cout<<"YES"<<endl;
		else cout<<"NO"<<endl;
	}
}

目录

hiho216 Gas Stations

题意分析:

1.题是什么?

2.思路

(1).二分查值

(2).大根堆贪心


hiho 216 Gas Stations

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

There are N gas stations on a straight, M kilo-meters long highway. The i-th gas station is Ai kilo-meters away from the beginning of the highway. (It is guaruanteed that there is one gas station at each end of the highway)

Now the mayor can build K more gas stations. He wants to minimize the maximum distance between two adjacent gas station. Can you help him?

输入

The first line contains 3 integer N, M, k. (2 <= N <= 1000, 1 <= M, K <= 100000)  

The second line contains N integer, A1, A2, ... AN. (0 = A1 <= A2 <= ... <= AN = M)

输出

The minimized maximum distance rounded to one decimal place.

样例输入

3 10 2  
0 2 10

样例输出

2.7

题意分析:

1.题是什么?

    现在有在长为m的路上有n个一直位置的加油站,并且起点终点肯定各有一个,现在你可以另外新建k个加油站,已任何两个加油站之间的最大距离最小化的方案为最优方案,问你这个最小化的最大距离是多少.

2.思路

(1).二分查值法

    最小化的最大距离,最小化最大值!够明显了吧,二分查值的标志,这里取l=0,r=道路长度,我们查的是满足对这n-1个段切最多k刀就可以使每个切完后的段小于等于x的最大的x,x就在0到道路长度之间,最后记得保留小数点后一位

                                       (C语言中%f会自动四舍五入到显示的最后一位小数)

ac代码

#include <stdio.h>
#include <math.h>
const int maxn=1001;
int n,m,k;
int a[maxn];
bool c(double t,int k){
	int num=0;
	for(int i=n-1;i>0;i--) num+=ceil(a[i]/t)-1; 
	return num<=k;
}
void solve(){
	scanf("%d%d%d",&n,&m,&k);
	for(int i=0;i<n;i++) scanf("%d",&a[i]);
	for(int i=n-1;i>0;i--) a[i]=a[i]-a[i-1];
	
	double l=0,r=m,mid;
	while(r-l>0.001){
		mid=(l+r)/2;
		if(c(mid,k)) r=mid; 
		else l=mid;
	}
	printf("%.1lf",r);
}
int main(){
	solve();
	return 0;
}

(2).大根堆贪心

    这里涉及到一个稀疏度的概念,什么是稀疏度呢:

    稀疏度的概念是对于原本的那n-1个段,就像样例输入

3 10 2 

0 2 10 

    有两个段长度分别为2和8是吧,此时稀疏度就是2和8,我发现8最大,故而砍第二个段一刀,稀疏度变为2和4,4还是大,故而第二刀还是砍它,砍了之后稀疏度变为2和2.7,具体实现时可以用pair作为单位的大根堆来实现,first为稀疏度,second为原始段序号,这样甚至不用重载比较规则,因为pair为单位时默认以first排序,最重要的就是要另外开一个数组length记录原始段长,再开一个num记录一下这n-1个段每个段现在是被砍成了几段,初值自然都该是1,

    之后通过出大根堆根节点,就能知道哪个段稀疏度最高,最高的是第i个段就砍他,新的稀疏度就是 length[i]/(float)(num[i]+1),将之重新压进大根堆,这样重复砍k刀.

ac代码

#include <iostream>
#include <iomanip>
#include <queue>
using namespace std;
typedef pair<float,int> pa;
const int maxn=1001;
const int maxa=100005;
int n,m,k;
int a[maxn];       //记录加气站位置 
int length[maxn];//记录原始段长
int num[maxn];   //记录每个段现在是被切成了几段 

void solve(){
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n-1;i++){
		length[i]=a[i+1]-a[i];
		num[i]=1;
	}
	priority_queue<pa> que;
	for(int i=1;i<=n-1;i++) que.push(make_pair(length[i],i));
	for(int i=0;i<k;i++){
		int temp=que.top().second; //该砍哪一段,这是序号也是下标 
		que.pop();
		que.push(make_pair(length[temp]/(float)(num[temp]+1),temp));//切x刀是分为x+1段,这是第一个+1,第二个+1是刚刚切的这一刀 
		num[temp]++;
	}
	cout<<fixed<<setprecision(1);
	cout<<que.top().first<<endl;
	cout.unsetf(ios::fixed);
	cout.precision(6);
}
int main(){
	solve();
	return 0;
}

hiho 217 Logic Expression Tree

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

You are given a logic expression tree of N nodes which are numbers from 1 to N. The leaf nodes are boolean values(TRUE, FALSE) and the non-leaf nodes are logic operators(AND, OR). The value of tree is the boolean value when you calculate the expression from leaves to the root. For example below is a logic expresssion tree whose valus is TRUE AND (FALSE OR TRUE) = TRUE.

      AND
     /   \
    T    OR
        /  \
       F    T

Now you want to reverse the value of the tree (from TRUE to FALSE or vice versa). You want to know how many operators at least are needed to be reversed (from AND to OR or vice versa). For example you only need to reverse the OR operator into AND to make the tree from TRUE to FLASE.

输入

The first line contains an integer N. (1 <= N <= 100)
The i-th line of the following N lines contains an integer Pi and a string Si. 
Pi denotes the number of the i-th node's parent node. Pi = 0 indicates that the i-th node is the root. (0 <= Pi <= N)
Si is either TRUE, FALSE, AND or OR denoting the boolean value or logic operator of the node.

输出

The minimum number of operators needed to be reversed. If you cannot reverse the value no matter how many operators are reversed output -1.

样例输入

5  
0 AND  
1 TRUE   
1 OR  
3 FALSE  
3 TRUE

样例输出

1

题意分析:

1.题是什么?

    就是给你一个逻辑表达树,叶节点为真或假,非叶节点为"AND"或"OR",其实就是与和或,每个结点的值为此节点的所有子节点的AND值或者OR值,整个树的真值理解为根节点的值.现在你可以把任意的AND改为OR,OR改为AND,翻转一次记作一次操作,问你最少需要几次翻转可令整个树的真值改变.

2.思路

    我们要求的是最少要几次操作,最少自然马上想到贪心,我们如果从树根开始尝试翻转,找尽可能靠近根节点的翻转方法,主观思维上想这样的方法可能就是最少的翻转了,因为越远离树根涉及的需要翻转的逻辑点就越多,然而只是可能,我就这个方法写了一个解答,交上去幸运的ac了,这个方法需要预处理出每个结点在子树不翻转情况下的值,并且我无法理论证明它的正确性.不过还是放在下面,后面有更正确的方法.

#include <iostream>
#include <vector>
using namespace std;
const int inf=(1<<30);

int n;
vector<pair<string,int> > a[101];

bool nodevalue[101]; //每个节点的值
bool prepare(int t1,int t2){ //递归预处理值
	bool res;
	string temp=a[t1][t2].first;
	int number=a[t1][t2].second;
	if(temp=="AND"){
		res=true;
		int num=a[number].size();
		for(int i=0;i<num;i++){
			string ts=a[number][i].first;
			int tn=a[number][i].second;
			if(ts=="TRUE") nodevalue[tn]=true;
			else if(ts=="FALSE"){
				nodevalue[tn]=false;
				res=false; 
			}
			else{
				bool tp=prepare(number,i);
				nodevalue[tn]=tp;
				res=res&&tp;
			}
		}
	}
	else if(temp=="OR"){
		res=false;
		int num=a[number].size();
		for(int i=0;i<num;i++){
			string ts=a[number][i].first;
			int tn=a[number][i].second;
			if(ts=="TRUE"){
				nodevalue[tn]=true;
				res=true;
			}
			else if(ts=="FALSE") nodevalue[tn]=false; 
			else{
				bool tp=prepare(number,i);
				nodevalue[tn]=tp;
				res=res||tp;
			}
		}
	}
	return res;
} 

int getans(int t1,int t2){ //递归求答案
	int ans=inf;
	string temp=a[t1][t2].first;
	int number=a[t1][t2].second;
	
	bool existtrue=false,existfalse=false;
	int num=a[number].size();
	for(int i=0;i<num;i++){
		int tn=a[number][i].second;
		if(nodevalue[tn]) existtrue=true;
		else existfalse=true;
	}
	if(existtrue&&existfalse) ans=1;
	else{
		for(int i=0;i<num;i++){
			string ts=a[number][i].first;
			if(ts=="AND"||ts=="OR"){
				ans=std::min(ans,getans(number,i));
			}
		}
		if(ans!=inf){
			if(existtrue&&temp=="OR") ans+=1;
			if(existfalse&&temp=="AND") ans+=1; 
		}
	}
	return ans;
}

int main(){
	cin>>n;
	int t;
	string ts;
	for(int i=1;i<=n;i++){
		cin>>t>>ts;
		a[t].push_back(make_pair(ts,i));
	}
	
	nodevalue[1]=prepare(0,0);
	
	int ans=getans(0,0);
	if(ans==inf) ans=-1;
	cout<<ans<<endl;
	return 0;
}

    在看过讨论之后,带入了树形dp的思维,发现如果自底向上的去进行dp确实可以准确的推出每个结点的所统领的子树的最小翻转次数,最终的根节点答案自然也就是准确的最小次数.

    这个超链接是 官方发出的讨论,可以参考一下.

    自底向上这个思维让我恍然大悟然后改写出了以下的解法:

#include <iostream>
#include <vector>
using namespace std;

const int maxn=101;
const int inf=(1<<30);

int n;
vector<int> a[maxn];
string nodestring[maxn];

bool nodevalue[maxn];
int f[maxn]; //dp数组,f[i]表示编号为i的结点所统领的子树想要改变值最少需要几次翻转,无法改变的情况被表示为inf
void getans(){
	for(int i=1;i<=n;i++) f[i]=inf; //初始化 
	
	for(int i=n;i>=1;i--){ //从n到1,这很重要
                //先计算结点i的值
		if(nodestring[i]=="TRUE") nodevalue[i]=true;
		else if(nodestring[i]=="FALSE") nodevalue[i]=false;
		else{
			if(nodestring[i]=="AND"){
				int num=a[i].size();
				nodevalue[i]=true;
				for(int j=0;j<num;j++) nodevalue[i]=(nodevalue[i]&&nodevalue[a[i][j]]); 
			}
			else if(nodestring[i]=="OR"){
				int num=a[i].size();
				nodevalue[i]=false;
				for(int j=0;j<num;j++) nodevalue[i]=(nodevalue[i]||nodevalue[a[i][j]]); 
			}

                        //再进行dp推算
			bool existtrue=false,existfalse=false;
			int num=a[i].size();
			for(int j=0;j<num;j++){
				int tn=a[i][j];
				if(nodevalue[tn]) existtrue=true;
				else existfalse=true;
			}
			if(existtrue&&existfalse) f[i]=1;
			else{
				for(int j=0;j<num;j++) f[i]=std::min(f[i],f[a[i][j]]);
				if(f[i]!=inf){
					if(existtrue&&nodestring[i]=="OR") f[i]+=1;
					if(existfalse&&nodestring[i]=="AND") f[i]+=1; 
				}
			}	
		}
	}
}

int main(){
	cin>>n;
	int parent;
	string ts;
	for(int i=1;i<=n;i++){
		cin>>parent>>ts;
		a[parent].push_back(i);
		nodestring[i]=ts;
	}
	
	getans();
	
	if(f[1]==inf) cout<<"-1"<<endl;
	else cout<<f[1]<<endl;

	return 0;
}

hiho 218 Keywords Filter

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

You are given a string S which consists of only lower case letters and N keyword W1, W2, ... WN which also consists of only lower case letters. For each appearance of a keyword in S you need to change the letters matching the keyword into '*'.

Assume S = "abcxyzabcd" and keywords are {"abc", "cd"}, the filtered S' will be "***xyz****".

输入

The first line contains an integer N. (1 <= N <= 1000)

The following N lines each contains a keyword. The total length of the keywords is no more than 100000.

The last line contains the string S. The length of S is no more than 100000.

输出

The filtered string.

样例输入

2  
abc  
cd  
abcxyzabcd

样例输出

***xyz****

题意分析:

1.题是什么?

    给你1000个长度为100000以内的屏蔽词,然后给你一段长度100000以内的文章,要把文章中所有屏蔽词都换成'*'号然后输出.

2.思路

(1)ac自动机?单纯kmp?

   字符串多模匹配,妥妥的ac自动机模版题啊,不过我个人有点没搞懂ac自动机的fail指针那个地方而且很久没写ac自动机了,故而ac自动机的解法后面会补上,下面放我只用kmp配合一些优化成功ac的解法:

    首先不知道kmp是什么算法的可以看我另一篇博客 kmp详解

(2)单纯kmp时间空间复杂度分析

    首先分析解法的时间复杂度,我们知道kmp时间复杂度是m+n的,故而如果单纯的用kmp,预计时间复杂度会是在1000*(100000+100000),也就是2亿,时间上勉强能接受,空间复杂度上需要先存下那1000个关键字,最极限需要1亿个char空间,空间上很悬然而我这样存没出事,看来输入是手下留情了的.

(3)解法以及关键优化

    解法是以对每个屏蔽词以kmp做匹配,匹配成功则屏蔽相应段,然而单纯这么做只能过90%数据,因为这么做存在时间复杂度暴涨的特例:

   对于文章aaaaaaaaaaaaaaaaaaaaaa,屏蔽词aaaaaa在文章中做kmp会多次匹配成功,每次都傻傻的屏蔽相应段会造成时间复杂度暴涨为(m-n)*n,几乎是m*n,故而我们需要做一下优化:

   导致时间复杂度暴涨的关键是我们重复屏蔽了某些段,故而我们只要做一个lastpingbi存最后屏蔽的地方,循环时以lastpingbi为辅助边界,就可以避免重复屏蔽,成功ac

(4)ac自动机做法

    不知道什么是ac自动机?参考我这篇博客吧 ac自动机详解

    其实用ac自动机就做成了模版题,自己将第三步模式匹配那里改一下,匹配成功时记录一下修改区间即可,代码如下,

    关于区间合并那里用到了一个小技巧,见此: 快速区间合并

ac代码

1.单纯kmp

#include <iostream>
using namespace std;

const int maxn=100005;
string keyword[1000];

int ntext[maxn];//ntext数组 
void getntext(string s){//构造ntext数组,真正的模版
	ntext[0]=-1;
	int i=0,j=-1; //j为什么初值赋值-1?0其实也行,仅仅是为了少一个判断, 
	while(s[i]){
		if(j==-1||s[i]==s[j]) ntext[++i]=++j; 
		else j=ntext[j];
	}
}

int lastpingbi; //这个非常重要,没有这个只能过90%数据
string wordsfilter(string a,string b,string ans){
	lastpingbi=0;
	int blen=0;
	while(b[blen]) blen++;
	getntext(b);
	int i=0,j=0;
	while(a[i]){
		if(j==-1||a[i]==b[j]){
			i++,j++;
			if(!b[j]){
				for(int l=i-1;l>=std::max(lastpingbi,i-blen);l--) ans[l]='*';
				lastpingbi=i-1;
				j=ntext[j];  
			}
		}
		else j=ntext[j];  
	}
	return ans;
}


void solve(){
	int n;
	cin>>n;
	for(int i=0;i<n;i++) cin>>keyword[i]; 
	string str,ans;
	cin>>str;
	ans=str;
	for(int i=0;i<n;i++) ans=wordsfilter(str,keyword[i],ans);
	cout<<ans<<endl;
}

int main(){
	solve();
	return 0;
}

2.ac自动机


#include <iostream>
#include <stdio.h>
#include <queue>
#include <string.h>
#include <malloc.h>
using namespace std;
const int maxkeywordlen=100001;
const int maxstrlen=100001; 
char keyword[maxkeywordlen],str[maxstrlen],ans[maxstrlen];
int vis[maxstrlen];

int charmapping[256]; //字符映射数组,charmapping[i]=x表示ascii码为i的字符对应于treenode中的next[x] 
void init_charmapping(){
	for(int i='a';i<='z';i++){ //我的这个字典树现在只允许输入小写字符组成的字符串,然而由于有charmapping的存在,增加新字符添加映射并且增大maxn就好,很方便. 
		charmapping[i]=i-'a';
	} 
}
 
struct node{
    node *fail;     //失败指针,此节点失配时将跳向fail所指节点 
	int end;        //此值表示以此节点为结尾的单词个数 
	node *next[26]; //指向子节点 
}root;

node *getnode(){  //构造 一个新结点并做好初始化,返回指向该节点的指针 
	node *newnode=(node *)malloc(sizeof(node));
	newnode->end=0;
	newnode->fail=NULL;
    for(int i=0;i<26;i++) newnode->next[i]=NULL;
    return newnode;
}
 
void insert(char *s){ //向ac自动机加入字符串s 
    int k=0,temp;
    node* t=&root;
	for(int i=0;s[i];i++){
		temp=charmapping[s[i]];
        if(!t->next[temp]) t->next[temp]=getnode();
        t=t->next[temp];
	}
    t->end=strlen(s);
}
 
void build_fail(){ //构造ac自动机的失败指针 
	queue<node *> q;
    q.push(&root);
    while(!q.empty()){
        node* t=q.front();
        q.pop();
        for(int i=0;i<26;i++){
            if(t->next[i]){
                if(t==&root) t->next[i]->fail=&root;
                else{
                    node *p=t->fail;
                    while(p!=NULL){
                        if(p->next[i]){
                            t->next[i]->fail=p->next[i];
                            break;
                        }
                        p=p->fail;
                    }
                    if(p==NULL) t->next[i]->fail=&root;
                }
                q.push(t->next[i]);
            }
        }
    }
}

void match(char *s){ //计算ac自动机中所有模式串在字符串s中出现的次数之和 
	int slen=strlen(s);
	for(int i=0;i<slen;i++) vis[i]=0; 
    node *p=&root;
    for (int i=0;i<slen;++i){
		while(p!=&root&&p->next[charmapping[s[i]]]==NULL) p=p->fail; 
		if (p->next[charmapping[s[i]]]!=NULL)
		{
			p =p->next[charmapping[s[i]]] ;
			if (p->end){
				vis[i-p->end+1]++;
				vis[i+1]--;
			}
			else{
				node *tn=p->fail;
				while(!tn->end&&tn!=&root){
					tn=tn->fail;
				}
				if(tn->end){
					vis[i-tn->end+1]++;
					vis[i+1]--;
				}
			}
		}
	}
	for(int i=1;i<slen;i++) vis[i]+=vis[i-1];
	for(int i=0;i<slen;i++) printf("%c",vis[i]>0?'*':s[i]);
}

int main(){
	init_charmapping();
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++){
		scanf("%s",&keyword);
		insert(keyword);
	}
	build_fail();
	scanf("%s",&str);
	match(str);
	return 0;
} 

hiho 219 Smallest Rectangle

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

You are given N 2D points P1, P2, ... PN. If there is a rectangle satisfying that its 4 vertices are in the given point set and its 4 sides are parallel to the axis we say the rectange exists.

Find the smallest exsisting rectange and output its area.

输入

The first line contains an integer N. (1 <= N <= 1000)

The following N lines each contain 2 integers Xi and Yi denoting a point (Xi, Yi). (0 <= Xi, Yi <= 1000000)

输出

Output the smallest area. If no rectangle exsists output -1.

样例输入

9  
0 0  
0 1   
0 4  
1 0  
1 1  
1 4  
4 0  
4 1  
4 4

样例输出

1

题意分析:

1.题是什么?

    给你n个坐标点,要你选其中四个组成一个平行x轴y轴的矩形,要求输出最小的矩形面积.

2.解题思路

    n大小为1000,选4个,n^4肯定不行,我们知道对角线的两个点就可以确定一个矩形,故而折半枚举左下右上两个点,复杂度n^2

    剩余左上右下两个点即可确定位置,通过对n个点排序后进行两次二分查找即可确定这两个点是否真实存在,总复杂度nlogn+n^2*2logn

    现在考虑进一步优化,开始的折半枚举毫无问题,不过后面两个是否存在的验证那里用哈希表可以以O(1)完成验证,且由于xy在1e6以内,我们可以把二维的 (x,y)对应为一维的x*1e6+y,进而小优化.

ac代码

1.折半枚举+二分查值

#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <math.h>
using namespace std;
typedef pair<int,int> pa;
typedef long long ll; 
const ll inf=1e12+5;
const int maxn=1e3+5;

pa a[maxn];
 
void solve(){
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++) scanf("%d%d",&a[i].first,&a[i].second);
	sort(a,a+n);
	ll ans=inf;
	for(int i=0;i<n-1;i++){
		for(int j=i+1;j<n;j++){
			ll x1=a[i].first,y1=a[i].second,x2=a[j].first,y2=a[j].second;
			ll area=abs(x2-x1)*abs(y2-y1);
			pa point1=make_pair(x1,y2),point2=make_pair(x2,y1); 
			if(area&&area<ans&&*lower_bound(a,a+n,point1)==point1&&*lower_bound(a,a+n,point2)==point2) ans=area; 
		}
	}
	if(ans==inf) printf("-1\n");
	else printf("%lld\n",ans);
}
 
int main(){
	solve();
	return 0;
}

2.折半枚举+哈希表

#include <iostream>
#include <unordered_set>
#include <stdio.h>
#include <math.h>
using namespace std;
typedef pair<int,int> pa;
typedef long long ll; 
const int maxn=1e3+5;
const ll maxm=1e6+5;
const ll inf=maxm*maxm+5;

pa a[maxn];
unordered_set<ll> s;
 
void solve(){
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++) scanf("%d%d",&a[i].first,&a[i].second);
	for(int i=0;i<n;i++) s.insert(a[i].first*maxm+a[i].second);
	ll ans=inf;
	for(int i=0;i<n-1;i++){
		for(int j=i+1;j<n;j++){
			ll x1=a[i].first,y1=a[i].second,x2=a[j].first,y2=a[j].second;
			ll area=abs(x2-x1)*abs(y2-y1);
			if(area&&area<ans&&s.count(x1*maxm+y2)&&s.count(x2*maxm+y1)) ans=area; 
		}
	}
	if(ans==inf) printf("-1\n");
	else printf("%lld\n",ans);
}
 
int main(){
	solve();
	return 0;
}

hiho 220 Push Button I

时间限制:5000ms

单点时限:1000ms

内存限制:256MB

描述

There are N buttons on the console. Each button needs to be pushed exactly once. Each time you may push several buttons simultaneously.

Assume there are 4 buttons. You can first push button 1 and button 3 at one time, then push button 2 and button 4 at one time. It can be represented as a string "13-24". Other pushing ways may be "1-2-4-3", "23-14" or "1234". Note that "23-41" is the same as "23-14".

Given the number N your task is to find all the pushing ways.

输入

An integer N. (1 <= N <= 8)

输出

Output the different pushing ways in lexicographical order.

For the same pushing way output the smallest string in lexicographical order as representative.

样例输入

3

样例输出

1-2-3
1-23
1-3-2
12-3
123
13-2
2-1-3
2-13
2-3-1
23-1
3-1-2
3-12
3-2-1

题意分析:

1.题是什么?

    给你个数字n,1<=n<=8,字典序升序输出由1到n的数字和符号'-'构成的所有字符串,要求不能重复,这个重复指的是由'-'隔开的数字集合不能重复,32和23就是重复的.

2.思路

    首先看到是按字典序输出所有字符串可能,妥妥的dfs填空,我们知道关于最简单的字符串字典序升序我们做一个flag数组记录哪些字符用过的即可,然后每层递归都由一个for循环由字典序升序逐个填入每个可用字符即可升序构造出所有不重复的字符串,

int rest;
void dfs(int nowpos){
	if(!rest){
		ans[nowpos]='\0'; 
		puts(ans);
		return;
	}
	for(int i=1;i<=n;i++){
		if(flag[i]){
			ans[nowpos]='0'+i;
			flag[i]=false;
			rest--;
			
			dfs(nowpos+1);
			
			rest++;
			flag[i]=true;
		}
	}
}

现在这个问题加深了难度,加入了一个'-'符号并且重定义了重复,认为两个字符串中每个'-'分隔开的部分都包含完全相同的字符集即视为重复

比如 12-34 与21-43也是重复的,故而我们对dfs需要做修改,一方面加入'-',一方面加入另一个标志位interrupt

'-'的规则很简单,不是第一个且前面也不是'-'则优先dfs填入,

而interrupt的设计目的是保证'-'所隔开的分块内字符为升序,interrupt存的值为当前分块内最大的字符,故而自然填入新的'-'即开始新的分块时要重置为0,填入新字符时更新interrupt,for循环也以interrupt对i做初始化;

ac代码

#include <stdio.h>
const int maxn=9;
int n;
char ans[2*maxn];
bool flag[maxn];
int rest;
int interrupt;
void dfs(int nowpos){
	if(!rest){
		ans[nowpos]='\0';//手动结尾,很重要 
		puts(ans);
		return;
	}
	if(nowpos&&ans[nowpos-1]!='-'){//不是第一个空并且前面不是'-' 
		ans[nowpos]='-';
		//修改标志 
		int temp=interrupt;
		interrupt=0;
		
		dfs(nowpos+1);
		//回溯标志 
		interrupt=temp;
	}
	for(int i=interrupt+1;i<=n;i++){
		if(flag[i]){
			ans[nowpos]='0'+i;
			
			//修改标志 
			flag[i]=false;
			rest-=1;
			int temp=interrupt;
			interrupt=i;
			
			dfs(nowpos+1);
			
			//回溯标志 
			interrupt=temp; 
			rest+=1;
			flag[i]=true;
		}
	}
}
int main(){
	scanf("%d",&n);
	rest=n;
	interrupt=0;
	for(int i=1;i<=n;i++) flag[i]=true; 
	dfs(0);
	return 0;
}

hiho 221 Push Button II

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

There are N buttons on the console. Each button needs to be pushed exactly once. Each time you may push several buttons simultaneously.

Assume there are 4 buttons. You can first push button 1 and button 3 at one time, then push button 2 and button 4 at one time. It can be represented as a string "13-24". Other pushing way may be "1-2-4-3", "23-14" or "1234". Note that "23-41" is the same as "23-14".

Given the number N your task is to find the number of different valid pushing ways.

输入

An integer N. (1 <= N <= 1000)

输出

Output the number of different pushing ways. The answer would be very large so you only need to output the answer modulo 1000000007.

样例输入

3

样例输出

13

题意分析:

1.题是什么?

    将问题抽象化就是给你n个数字1~n,将所有数字以'-'分块,要输出的答案就是不重复的分块方法总数(由于总数太大要求余)

    而什么是重复呢,分块个数相同且每个对应的分块中的数字组合是相同的则视为重复,就好像现在要计算1234四个数字的分块方法,其中两种方法12-34与21-43,第一个分块内都是1和2,第二个分块内都是3和4,故而他们是重复的.

2.思路

    这道题是hiho220的问题变形,故而可以顺手了解一下我关于 hiho220 的解题博客,那道题是要输出所有的方法是怎样的,n最大为8而已故而使用的dfs填空,这道题其实题干一模一样,不过改为要求输出所有方法数目,并不需要关心每个方法具体是怎样的组合,相应的n也变为上限1000,故而原本的dfs填空必爆.需要改变解法.

    我们发现我们现在只需要输出所有方法数目,并不关心每个方法具体组合,这种问题特征几乎是线性dp的标志.而题中n为1时答案确定为1,故而更坚定了是线性dp.

    现在方向确定为线性dp后判断时间空间,1000的数据量级做n*n的dp时间空间都没问题,然后思考最关键的dp递推公式,线性dp的关键在于找到问题在n处的答案与前方在n-1处的答案的联系即递推关系.

    针对与这个问题,我们列举当n为2时,答案为3: 12    1-2    2-1这三种,我们也可以将之看作:

        ?-?12-?           (1)

        ?-?1-?-?2-?      (2)

        ?-?2-?-?1-?      (3)

    ?表示为空,我们会发现如果我们想在n为2的答案的基础上推出n为3的答案,一定是基于n为2的某个答案,将数字3加入其某个分块或使数字3自成一个分块,上面的?就是代表所有的这两种情况.

    dp[i][j]中i与j的意义设计是线性dp最关键的一点,递推数组最初我设计为二维,dp[1000][1000],dp[i][j]我的设计中表示n为i时分块为j个的答案种数.

     而就像dp[3][2],表示n为3时有2个分块的答案总数,它自然来自于n=2时的答案的延伸,也就是

      (1).n=2时对分块为1的答案将3自成一个分块,1+1=2个分块

      (2).n=2时对分块为2的答案将3加入其中某个分块

    只有这两种情况可以延伸出dp[3][2].故而递推公式 dp[i][j]=j*dp[i-1][j-1]+j*dp[i-1][j];

for(int i=0;i<=n;i++) for(int j=0;j<=n;j++) dp[i][j]=0;
for(int i=1;i<=n;i++){
        dp[i][1]=1;
        for(int j=2;j<=n;j++) dp[i][j]=(j*(dp[i-1][j-1]+dp[i-1][j]))%mod;
}

用时13ms,这里发现n的答案种数仅仅与n-1有关,顺手用了个滚动数组节约一下空间,不懂滚动数组可以来我的博客先了解一下 滚动数组  优化后:

int now=0;
for(int i=1;i<=n;i++){
	now=(now+1)%2;
	dp[now][1]=1;
	for(int j=2;j<=n;j++) dp[now][j]=(j*(dp[(now+1)%2][j-1]+dp[(now+1)%2][j]))%mod;
	dp[now][i+1]=0; //特殊的初始化方式
}

用时6ms,然后又发现好像滚动数组都用不着,因为数据的递推方式很特别,于是最终发现了最优解法建立于一维数组:

for(int i=1;i<=n;i++){
	dp[1]=1;
	for(int j=i;j>=2;j--) dp[j]=(j*(dp[j-1]+dp[j]))%mod;
	dp[i+1]=0; //特殊的初始化方式
}

用时3ms AC,再想了想.....应该没什么可优化的了,花15分钟从13ms优化到了3ms,用900000ms的时间换来的10ms的优化,emmmm....不知道说什么好.....

ac代码:

#include <stdio.h>
typedef long long ll;
const int maxn=1005;
const ll mod=1000000007;
ll dp[maxn];
void solve(){
	int n;
	scanf("%d",&n);
	
	for(int i=1;i<=n;i++){
		dp[1]=1;
		for(int j=i;j>=2;j--) dp[j]=(j*(dp[j-1]+dp[j]))%mod;
		dp[i+1]=0; //特殊的初始化方式
	}
	ll ans=0;
	for(int i=1;i<=n;i++) ans=(ans+dp[i])%mod; 
	printf("%lld\n",ans);
}
//https://blog.csdn.net/qq_31964727/article/details/82886990 解题博客了解一下
int main(){
	solve();
	return 0;
}

ho 225 Inside Triangle

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

Determine if a point is inside a 2D triangle.

输入

The first line contains an integer T denoting the number of test case. (1 <= T <= 10)

The following T lines each contain 8 integers, Px, Py, Ax, Ay, Bx, By, Cx, Cy. P denotes the point. ABC denotes the triangle. The coordinates are within [-1000000, 1000000].

输出

For each test case output YES or NO denoting if the point is inside the triangle. Note that the point is considered inside the triangle if the point is just on some side of the triangle.

样例输入

2  
0 1 -1 0 1 0 0 2  
0 0 0 1 1 0 1 1

样例输出

YES  
NO

题意分析:

1.题是什么?

    题倒是清晰明了,给你四个点p,a,b,c,判断p是否在三角形abc内

2.思路

    我有两种处理大方向:

     第一种面积比较思路,计算S_{pab}+S_{pbc}+S_{pac}与S_{abc}比较,与S_{abc}比较,相等则在三角形内,否则不在,至于为什么会这样子画个草图看看就明白了.

     第二种基于p点位置做判断思路,核心思维为判断p和a是否在直线bc的同侧,p和b是否在直线ac的同侧,p和c是否在直线ab的同侧,三个都满足则p在abc中.

   (1).面积法

    第一种面积法,已知三点(x1,y1),(x2,y2),(x3,y3)时S=fabs(0.5*(x_{1}*(y_{2}-y_{3})+x_{2}*(y_{3}-y_{1})+x_{3}*(y_{1}-y_{2})))

    以此公式直接计算S_{pab}+S_{pbc}+S_{pac}与S_{abc}比较S_{abc}比较即可,实际代码中S计算被我去掉了0.5,避免精度问题,不影响最终结果.

下面由第二种思路衍生两种方法:

    (2).点代一般式法

    首先根据ab点求出直线ab一般式方程,然后将p与c分别代入ab一般式方程结果相乘为负则p与c不在ab的同侧,p与a,p与b同样处理,三个皆同侧,即p在abc内.

    tips:这里我的算法中直线一般式可能不是最简形式,故而中间数据大小可能达到1000000^{4},会爆掉long long,故而我对于getpsbaseonline()函数的返回值做了缩小,仅仅保留我需要的正负特征,这一步必不可少.

    (3).矢量法

    首先了解一下向量叉积几何意义之一:

    有向量A(x1,y1),向量B(x2,y2),叉积结果A*B=x1y2-x2y1是一个标量,如果它等于0表示AB在同一直线上,大于0表示A在B的顺时针方向,小于0表示A在B的逆时针方向

    故而实现核心为通过向量叉积结果的正负判断点在直线的哪一侧,其它的处理就与点代一般式方法近似了.

ac代码

    (1).面积法

#include <stdio.h>
#include <math.h>
typedef long long ll;

void solve(){
	int t;
	scanf("%d",&t); 
	for(int i=0;i<t;i++){
		ll px,py,ax,ay,bx,by,cx,cy;
		scanf("%lld%lld%lld%lld%lld%lld%lld%lld",&px,&py,&ax,&ay,&bx,&by,&cx,&cy);
		//判断是否不在三角形内 
		if(fabs(px*(by-cy)+bx*(cy-py)+cx*(py-by))+fabs(ax*(py-cy)+px*(cy-ay)+cx*(ay-py))+fabs(ax*(by-py)+bx*(py-ay)+px*(ay-by))==fabs(ax*(by-cy)+bx*(cy-ay)+cx*(ay-by))) printf("YES\n");
		else printf("NO\n");
	}
	
}

int main(){
	solve();
	return 0;
}

    (2)点代一般式法

#include <stdio.h>
typedef long long ll;
struct point{ //(x,y)
	ll x,y;
};

struct line{ //ax+by+c=0
	ll a,b,c;
};

line getlinebytwopoint(point p1,point p2){ //已知两点求直线一般式 
	line res;
	res.a=p2.y-p1.y;
	res.b=p1.x-p2.x;
	res.c=p2.x*p1.y-p1.x*p2.y; 
	return res;
}

ll getpsbaseonline(point p,line l){ //将点带入一般式获知点在左上侧还是右下侧,大于0左上,等于0线上,小于0右下,返回值仅保留正负特征 
	ll temp=l.a*p.x+l.b*p.y+l.c;
	if(temp==0) return 0; 
	return temp>0?1:-1;
}

void solve(){
	int t;
	scanf("%d",&t); 
	for(int i=0;i<t;i++){
		point pointp,pointa,pointb,pointc;
		scanf("%lld%lld%lld%lld%lld%lld%lld%lld",&pointp.x,&pointp.y,&pointa.x,&pointa.y,&pointb.x,&pointb.y,&pointc.x,&pointc.y);
		line lineab,linebc,lineac;
		lineab=getlinebytwopoint(pointa,pointb);
		linebc=getlinebytwopoint(pointb,pointc);
		lineac=getlinebytwopoint(pointa,pointc);
		
		bool ans=true;
		//判断是否不在三角形内 
		if(getpsbaseonline(pointa,linebc)*getpsbaseonline(pointp,linebc)<0||getpsbaseonline(pointb,lineac)*getpsbaseonline(pointp,lineac)<0||getpsbaseonline(pointc,lineab)*getpsbaseonline(pointp,lineab)<0) ans=false;
		
		if(ans) printf("YES\n");
		else printf("NO\n");
	}
	
}

int main(){
	solve();
	return 0;
}

    (3)矢量法

#include <stdio.h>
typedef long long ll;

struct point{
	ll x,y;
};

struct myvector{
	ll x,y;
};

myvector getmyvectorbytwopoint(point p1,point p2){
	myvector res;
	res.x=p2.x-p1.x;
	res.y=p2.y-p1.y;
	return res;
}

ll getpsbaseonmyvector(point p,point p1,point p2){
	myvector p1p2=getmyvectorbytwopoint(p1,p2);
	myvector p1p=getmyvectorbytwopoint(p1,p);
	ll temp=p1p2.x*p1p.y-p1p.x*p1p2.y; //叉积结果 
	if(temp==0) return 0; 
	return temp>0?1:-1;
}


void solve(){
	int t;
	scanf("%d",&t);
	for(int i=0;i<t;i++){
		point pointp,pointa,pointb,pointc;
		scanf("%lld%lld%lld%lld%lld%lld%lld%lld",&pointp.x,&pointp.y,&pointa.x,&pointa.y,&pointb.x,&pointb.y,&pointc.x,&pointc.y);
		bool ans=true;
		
		//判断是否不在三角形内 
		if(getpsbaseonmyvector(pointa,pointb,pointc)*getpsbaseonmyvector(pointp,pointb,pointc)<0||getpsbaseonmyvector(pointb,pointa,pointc)*getpsbaseonmyvector(pointp,pointa,pointc)<0||getpsbaseonmyvector(pointc,pointa,pointb)*getpsbaseonmyvector(pointp,pointa,pointb)<0) ans=false;
		
		if(ans) printf("YES\n");
		else printf("NO\n");
	}
}

int main(){
	solve();
	return 0;
}

hiho 226 Ctrl-C Ctrl-V

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

Yor are bored. So you open a notepad and keep pressing letter 'A' to type a lot of 'A's into the text area.

Suddenly an idea come out. If you can do the following 4 kinds of operations N times, how many 'A's you can type into the text area?    

- A: Type a letter A into the text area

- Ctrl-A: Select all the letters in the text area

- Ctrl-C: Copy the selected letters into clipboard  

- Ctrl-V: Paste the letters from clipboard into the text area

Assume N=7 you can get 9 'A's by doing following operations: A, A, A, Ctrl-A, Ctrl-C, Ctrl-V, Ctrl-V.  

输入

An integer N. (1 <= N <= 1000000000)

输出

The maximum number of 'A's you can type into the text area. Note the answer may be very large so output the answer modulo 1000000007.

样例输入

7

样例输出

9

题目分析:

1.题是什么?

    题就是给你一个十亿以内的数字n,代表你可以进行刚好n次以下操作,

    A操作:向当前字符串末尾添加一个字符'A'

    Ctrl-A操作:选定当前整个字符串

    Ctrl-C操作:把当前选定的字符串复制到剪贴板

    Ctrl-V操作:把剪贴板中的字符串粘贴到当前字符串末尾

   你要输出的就是通过n次操作你能作出的最长字符串有多长,结果取模1000000007

2.思路

    十亿的数据大小,别说暴力,n复杂度也不合适,故而猜测不同大小的n的答案之间必然有规律,或者说一定有一种简单的最优操作可以重复之然后达到最优答案,否则以低于n复杂度完成是不可能的.

    故而我们可以简单的思考一下最优解规律,首先

        (1).Ctrl-A+Ctrl-C的顺序是不可颠倒的,

        (2).Ctrl-A+Ctrl-C+Ctrl-V中间插入A操作无论插在哪儿必然不如A+Ctrl-A+Ctrl-C+Ctrl-V,可以自己分析一下,

        (3).Ctrl-A+Ctrl-V+Ctrl-C也必定不如Ctrl-A+Ctrl-C+Ctrl-V,因为Ctrl-V能贴出来的必定越来越长,大于等于之前的Ctrl-V效果,

        (4).经过简单的穷举可以知道当n<7时无论如何操作最优解为n,当n>=时最优解必存在Ctrl-A+Ctrl-C+Ctrl-V操作

     故而我们知道最优解中Ctrl-A+Ctrl-C+Ctrl-V必然是一个整体,Ctrl-A,Ctrl-C单独出现也是无意义的.n>=7时A操作一定在最前,因为在做了Ctrl-A+Ctrl-C之后每一个Ctrl-V操作肯定优于A操作,因为剪贴板里的长度肯定大于等于1.

    我自己的思路最开始是错了的,在个人总结中分析错误点,这里我给出官方的正确思路,其实关于答案是可以dp得到的,因为对于最优操作最后一个操作必然是A或者Ctrl-V,因为Ctrl-A,Ctrl-C并不实际增加长度,不可能放在最后一个,我们甚至可以这样子,前6个穷举直接赋值,因为当n>=7时最后一个操作必然是Ctrl-V,原因上面也总结了,故而可以得到dp的递推公式(n>=7):

                                操作是Ctrl-V:假设最优解最后有k个连续Ctrl-V,则dp[i]=dp[i-2-k]*(k+1);

        (dp[i-2-k]其实就是此时剪切板中字符串的长度,这里i之所以减去k+2是减去了k个连续的Ctrl-V以及前面连着的Ctrl-A+Ctrl-C,不减去前面连着的Ctrl-A+Ctrl-C你得到的可能不是剪切板中字符串的长度!,乘以k+1是k个粘贴来的加上原本的1个)

    初始状态的最优解穷举就可以得到,即n<7时最优解为n,之后每一步递推因为要穷举k的可能性,故而dp复杂度为O(n^{2}),十亿大小的数据自然不可能以此为解法,不过我们可以以此解法得出n为1到100的答案进而研究答案规律,答案如下:

void solve(){
	int n;
	scanf("%d",&n);
	//小于7时直接赋值
	for(int i=0;i<7;i++) dp[i]=i;
	for(int i=7;i<100;i++){
		dp[i]=0;
		//穷举K
		for(int k=i-3;k>0;k--) dp[i]=max(dp[i],dp[i-2-k]*(k+1)%mod); 
	}
	for(int i=1;i<100;i++){
		printf("%d %lld\n",i,dp[i]);
	}
}

    观察答案可得规律: n>=16时,dp[i] = dp[i-5]*4;换句话说,当n足够大的时候,最优的策略就是不断的用Ctrl-A+Ctrl-C+Ctrl-V+Ctrl-V+Ctrl-V这么5个操作把长度变成4倍.dp[i] = dp[x]*4^{k}(11<=x<= 15且i=x+5k),4^{k}必须使用快速幂算法,不知道快速幂算法的可以移步我的博客了解一下 快速幂算法

3.ac代码

#include <stdio.h>
typedef long long ll;
const ll mod=1000000007;
ll max(ll a,ll b){ return a>b?a:b; }

ll fastpower(ll a,ll b)     
{     
    ll res=1;     
    ll temp=a;     
    while(b){    
       if(b&1) res=res*temp%mod;     
       temp=temp*temp%mod;     
       b>>=1;  
    }
    return res;     
}

void solve(){
	int n;
	scanf("%d",&n);
	ll dp[16];
	//小于7时直接赋值
	for(int i=0;i<7;i++) dp[i]=i;
	for(int i=7;i<16;i++){
		dp[i]=0;
		//穷举K
		for(int k=i-3;k>0;k--) dp[i]=max(dp[i],dp[i-2-k]*(k+1)%mod); 
	}
	if(n<16) printf("%lld",dp[n]);
	else{
		int x=(n-11)%5+11;
		printf("%lld",dp[x]*fastpower(4,(n-x)/5)%mod); 
	}
}

int main(){
	solve();
	return 0;
}

4.个人总结

    我在看到这道题数据大小是十亿时第一时间就否定了dp思路,尽管这道题有很多dp的特征,最优解,线性变化,选择操作,初始解固定等等,可我仅仅因为数据大小不能执行dp就否认了dp思路这是错误的,即时最终答案不能直接dp出来,也许可以借用dp找到答案的规律,

    在否认dp思路之后我根据对最优解特征的分析得出了一种错误结论:最优解一定是前面全是A操作,中间全是Ctrl-A+Ctrl-C+Ctrl-V,结尾全部是Ctrl-V这样子的结构,其实这是错误的,前面全A是正确的,结尾全是Ctrl-V也是正确的,可是中间部分并不一定是连续的Ctrl-A+Ctrl-C+Ctrl-V,有一个反例:

    当n为11时,A+A+A+A+A+Ctrl-A+Ctrl-C+Ctrl-V+Ctrl-V+Ctrl-V+Ctrl-V是我的算法得到的答案,为25

    可是实际上存在更优解A+A+A+Ctrl-A+Ctrl-C+Ctrl-V+Ctrl-V+Ctrl-A+Ctrl-C+Ctrl-V+Ctrl-V,为27,

    我固执的认为独立于Ctrl-A+Ctrl-C+Ctrl-V的单个的Ctrl-V都在末尾是错误的,虽然将这单个Ctrl-V后置后出来的一定是更多的,可是选择在某个Ctrl-A+Ctrl-C前面Ctrl-V会使后面的所有Ctrl-V收益更大,而相比较哪个大就不确定了,我正是忽略了这个,导致了以下错误解法

5.我的错误解法

void solve(){
	int n;
	scanf("%d",&n);
	if(n<7) printf("%d",n);
	else{
		ll ans=0;
		for(int i=1;i<20;i++){
			for(int j=0;j<20;j++){
				if(i+j<n&&(n-i-j)%3==0){
					ll temp=i;
					temp=i*kuaisumi(2,(n-i-j)/3)%mod;
					temp=(temp+j*(temp/2))%mod;
					if(temp>ans) ans=temp;
				}
			}
		}
		printf("%lld",ans);
	}
}

hiho 227 Longest Subsequence

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

You are given a set of N words W1, W2, ... WN and a target string S. Find the longest word which is also a subsequence of S.

输入

The first line contains an integer N. (1 <= N <= 10000)

The following N lines each contain a word Wi. (1 <= Σ|Wi| <= 100000)

The last line contains the string S. (1 <= |S| <= 100000)

输出

The length of the longest word which is also a subsequence of S.

样例输入

3  
a  
ac  
abc  
acbc

样例输出

3

题目分析:

1.题是什么?

    给你n个字符串(n最大10000),总长度不会大于100000;再给你一个目标字符串target(长度1000000),问你这n个字符串中是target的子序列的最长的串长度是多少?

2.思路

  (1).我的思路:

    既然我们只是要寻找最长的满足是子序列的a串,也许我们只需要挨个判断当前a串是否是目标b串的子序列,最后比较留下最大的就好,单纯的通过跑目标b串得知某个字符串是否是子序列最坏情况需要|b|=1000000,10000个就是一百亿,不行,

    由于已知当前串总长度也不过100000,故而多数a串都是短串,我们大可基于a串去比较,我的方法就是通过预处理b串中每种字符的位置有序存于vector,通过二分查找是否存在位置合适的某字符,去判断是否子序列.

  (2).官方思路

   通过预处理目标串得知每个位置后方第一个字符a,第一个字符b,第一个字符c,....的位置,这样可以直接遍历a串的同时进行跳表来判断是否是子序列,断开了就不是.

3.ac代码

(1).我的方法(时间复杂度1000000+100000*20(对1000000做二分最多20次),空间复杂度100000)

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn=10001;
const int maxascii=256;//全字符集映射于vector

string s[maxn];
string target;
vector<int> v[maxascii+1];
vector<int>::iterator ite;
void solve(){
	std::ios::sync_with_stdio(false);
	int n;
	cin>>n;
	for(int i=0;i<n;i++) cin>>s[i];
	cin>>target;
	
	for(int i=0;target[i];i++){
		v[target[i]].push_back(i);
	}
	int ans=0;
	for(int i=0;i<n;i++){
		int pos=-1,len;//pos记录当前匹配的位置,len记录字符串长度 
		for(len=0;s[i][len];len++){
			int temp=s[i][len];
			//通过二分查找大于上一个字符的匹配位置的当前字符位置
			ite=upper_bound(v[temp].begin(),v[temp].end(),pos);
			//目标串中找到了则迭代pos,继续,否则退出循环 
			if(ite!=v[temp].end()) pos=*ite; 
			else break;
		}
		//全部匹配才迭代ans
		if(!s[i][len]) ans=max(ans,len);
	}
	cout<<ans<<endl;
}

int main(){
	solve();
	return 0;
}

(2).官方方法(时间复杂度1000000*26+100000,空间复杂度26*1000000)

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn=10001;
const int maxascii=26;
const int maxtargetlen=1000001;

string s[maxn];
string target;
int nextchar[maxtargetlen][maxascii];
void solve(){
	std::ios::sync_with_stdio(false);
	int n;
	cin>>n;
	for(int i=0;i<n;i++) cin>>s[i];
	cin>>target;
	
	int targetlen=target.length();
	for(int i=0;i<maxascii;i++) nextchar[targetlen][i]=0;
	for(int i=targetlen;i>=0;i--){
		for(int j=0;j<maxascii;j++) nextchar[i][j]=nextchar[i+1][j];
		nextchar[i][target[i]-'a']=i+1;
	}
	int ans=0;
	for(int i=0;i<n;i++){
		int temp=0,len;
		for(len=0;s[i][len];len++){
			temp=nextchar[temp][s[i][len]-'a'];
			if(!temp) break;
		}
		if(!s[i][len]) ans=max(ans,len);
	}
	cout<<ans<<endl;
}

int main(){
	solve();
	return 0;
}

hiho 228 Parentheses Matching

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

Given a string of balanced parentheses output all the matching pairs.

输入

A string consisting of only parentheses '(' and ')'. The parentheses are balanced and the length of the string is no more than 100000.

输出

For each pair of matched parentheses output their positions in the string.

样例输入

(())()()

样例输出

1 4  
2 3  
5 6  
7 8

题意分析:

1.题是什么?

    就是给你一堆只由左右括号组成的字符串s,长度小于1e5,让你从左到右配对(输入保证肯定是一一配对的),记录配对的左右括号的位置.最后根据左括号位置从小到大输出位置对.

2.思路

    签到题做起来还是很舒服的,直接从左到右扫描字符串,准备好一个栈,我用数组模拟的,遇到左括号就将他位置压进栈,遇到右括号就将栈顶的左括号位置出栈与之配对.注意!!!边配对边输出答案顺序是不对的.

    我的做法是做一个ans数组保存答案,ans数组处理完之后的效果为:

                若字符串中i位置是'(',ans[i]值为与之配对的右括号位置,

                若字符串中i位置是')',ans[i]值为-1

具体实现有点技巧,就是while中else里面那两行

3.ac代码

#include <stdio.h>
const int maxn=1e5+3;
char s[maxn];
int ans[maxn];
int pos[maxn/2],num;

int main(){
	scanf("%s",&s);
	
	num=0;
	int index=0;
	while(s[index]){
		if(s[index]=='(') pos[num++]=index; 
		else{
			ans[index]=-1;
			ans[pos[--num]]=index;
		}
		index++;
	}
	
	for(int i=0;i<index;i++) if(~ans[i]) printf("%d %d\n",i+1,ans[i]+1); 
	return 0;
}

hiho 229 Same Letters In A Row

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

Litter Ho has a string of lowercase letters. He wants to re-order the letters so that the same letters are placed together in a row.

Unfortunately he can do at most K times of swaps. Each time he can only swap two letters. What is the maximum number of the consective same letters?

Assume the string is "bababbaa" and K = 1. By 1 swapping Little Ho can get "aabbbbaa". "bbbb" has length 4 which is also the maximum number of the consective same letters Little Ho can get.  

输入

The fist line contains an integer K. (1 <= K <= 100000)  

The second line contain a string of lowercase letters. The length is no more than 100000.

输出

Output the answer.

样例输入

1  
bababbaa

样例输出

4

题意分析:

1.题是什么?

    给你一个十万长度的小写字母字符串,问你在做出最多k次交换操作之后,得到的字符串中最长连续相同字符的最大长度为长.

2.思路

    最长连续相同字符只有26种可能,a,b或c....分别以abc..做目标求一下能在k次交换内得到的最长长度,其中最大的即是答案.

    以在bababbaa中找a在k次交换下能得到的最长长度为例,其实就是要找某一子串[l,r)满足下面两个条件:

        (1).其中不是a的字母的总数x小于等于k

              (这样才可能在最多k次交换之后[l,r)内全是a,动态更新一个nowk记录当前[l,r)内不是a的字母的总数,以1复杂度完成判定)

        (2).剩余串[0,l)与[r,n)中的a的个数大于等于x

             (就算你交换次数足够,没那么多a给你换有什么用.预处理一个count数组记录每种字符有多少个就能以1复杂度完成判定)

不断的找符合上面两个条件的子串,找到一个就更新一下ans,保留最大的.找完26种字母即为答案.

找子串这一步可以用我的方法,设立左右点l,r,条件递推,可以在近乎n复杂度下找完a下的所有子串,故而总复杂度26*n,搞定.

3.ac代码

#include <stdio.h>
typedef long long ll;
const int maxn=100005;
char s[maxn]; 
int count[26];
void solve(){
	int k;
	scanf("%d%s",&k,&s);
	for(int i=0;i<26;i++) count[i]=0;
	for(int i=0;s[i];i++) count[s[i]-'a']++;
	int ans=0;
	for(int i=0;i<26;i++){
		char cmpchar='a'+i;
		int l=0,r=0;
		int tempans=0,nowk=0;
		while(s[r]){
			while(nowk<=k&&nowk<count[i]-(r-l-nowk)&&s[r]){
				if(s[r]!=cmpchar){
					if(nowk<k&&nowk<count[i]-(r-l-nowk)) nowk++;
					else break;	
				}
				r++;
			}
			if(tempans<r-l) tempans=r-l;
			while(s[l]){
				if(s[l]!=cmpchar){
					l++;
					nowk--;
					break;
				}
				l++;
			}
		}
		if(ans<tempans) ans=tempans;
	}
	printf("%d\n",ans);
}

int main(){
	solve();
	return 0;
}

hiho 230 Smallest Substring

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

Given a string S and an integer K, your task is to find the lexicographically smallest string T which satisfies:  

1. T is a subsequence of S

2. The length of T is K.

输入

The first line contain an integer K. (1 <= K <= 100000)

The second line contains a string of lowercase letters. The length of S is no more than 100000.

输出

The string T.

样例输入

4  
cacbbac

样例输出

abac

题意分析:

1.题是什么?

    给你一个十万长度的字符串s,和十万以内的数字k,问你长度为k的字典序最小的s的子序列是什么?

2.思路

(1).大方向

    先定义一下,ans[i]表示ans中第i个字符,slen表示原串长度,设l=ans[i-1]来源自s中的位置+1,r=slen-(k-i),ans[i]应该是选自原串s中 [l,r] 之间的最靠前的字典序最小的字符

    左边界很容易理解,ans中第i个字符自然是选自第i-1个字符所选位置之后(代码中spos就是动态记录的第i-1个字符所选位置)

    右边界是为了保证选完第i个后剩下的可选字符个数至少有k-i个,一定能凑够k个长度的答案.毕竟字典序比较是一种从左到右的贪心比较,只要这个足够小,后面的是什么无所谓,只要有那么长就够了.

(2).我的思路

    我这里第一次实现是以暴力区间搜索完成的,实际最劣复杂度其实是n*n,重复扫描了太多,故而只能过90%的数据

//tle代码
const int maxn=1e5+5;

void solve(){
	int k;
	char s[maxn];
	char ans[maxn];
	scanf("%d%s",&k,&s);
	int slen=strlen(s);
	
	int anspos=0,spos=0;
	while(anspos<k){
		char temp='z'+1;
		for(int i=spos;i<=slen-k+anspos;i++){
			if(s[i]<temp){
				temp=s[i];
				spos=i+1;
			}
		}
		ans[anspos++]=temp;
	}
	ans[anspos]='\0';
	printf("%s\n",ans);
}

    这后面我通过预处理每个字符的位置,存进vector,然后在确定ans[i]是什么时,就从'a'到'z'扫描哪个字符所对应的vector中先出现符合条件的位置.实际实现我是用数组模拟vector纯c语言实现.效率上分析其实复杂度最劣为26*maxk+maxslen,虽然名为O(n)复杂度,可是与官方提供的O(nlogn)方法相比应该更慢..毕竟logn还是很快

(3).官方思路

       官方分析:http://hihocoder.com/discuss/question/5616 

       官方思路主要是想通过维持一个小根堆或者以set平衡二叉树的性质来实现对(字符,位置)键值对的排序,以logn完成对每个无效字符的删除.并选取k个有效的最小的.

(4).RMQ思路

        其实看完大方向之后就该想到一个经典问题,RMQ区间最小值问题,不过这里的'最小'需要重新定义,这里可以以 线段树做的RMQ 做实现,

3.ac代码

(1).我的思路

#include <stdio.h>
#define maxcharnum 26
const int maxn=1e5+5;
const int inf=1e8;
char s[maxn];
char ans[maxn];

//模拟vector<int> pos[maxcharnum];
int pos[maxcharnum][maxn];
int size[maxcharnum];

void solve(){
	int k;
	scanf("%d%s",&k,&s);
	
	//初始化'vector'
	for(int i=0;i<maxcharnum;i++) size[i]=0;
	//预处理每种字符的位置进'vector',顺手把字符串长度slen做好了
	int slen=0;
	while(s[slen]){
		int temp=s[slen]-'a';
		pos[temp][size[temp]++]=slen;
		slen++;
	}
	//inf做收尾限制,必不可少
	for(int i=0;i<maxcharnum;i++) pos[i][size[i]++]=inf;
	
	int index[maxcharnum];
	for(int i=0;i<maxcharnum;i++) index[i]=0; 
	int anspos=0,spos=0;//spos记录ans[i-1]选取自s中的位置+1
	while(anspos<k){
		for(int i=0;i<maxcharnum;i++){
			while(pos[i][index[i]]<spos) index[i]++;//删除无效的位置,这就是+maxslen的来源
			if(pos[i][index[i]]>=spos&&pos[i][index[i]]<=slen-(k-anspos)){
				spos=pos[i][index[i]++]+1;
				ans[anspos]='a'+i;
				break;
			}
		}
		anspos++; 
	}
	ans[anspos]='\0';//手动结尾字符串
	printf("%s\n",ans);
}

int main(){
	solve();
	return 0;
}

hiho 231 小Ho的强迫症

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

小Ho在一条笔直的街道上散步。街道上铺着长度为L的石板,所以每隔L距离就有一条石板连接的缝隙,如下图所示。

小Ho在散步的时候有奇怪的强迫症,他不希望脚踩在石板的缝隙上。(如果小Ho一只脚的脚尖和脚跟分别处于一条缝隙的两侧,我们就认为他踩在了缝隙上。如果只有脚尖或脚跟接触缝隙,不算做踩在缝隙上)  

现在我们已知小Ho两只脚的长度F以及每一步步伐的长度D。如果小Ho可以任意选择起始位置,请你判断小Ho能否保持不踩缝隙散步至无穷远处?

输入

第一行是一个整数T,表示测试数据的组数。

每组测试数据包含3和整数L, F和D,含义如上文所述。

对于30%的数据: L <= 1000  

对于60%的数据: L <= 100000

对于100%的数据: L <= 100000000, F <= 100000000, D <= 100000000, T <= 20

输出

对于每组数据输出一行YES或者NO,表示小Ho是否能走到无穷远处。

样例输入

2  
60 26 60  
30 26 75 

样例输出

YES  
NO

题意分析:

1.题是什么?

    一条石板路,石板之间的缝隙极细不考虑,石板长l,脚长f,步长d,任意位置开始走,问你可否走到无穷远出不踩缝隙上.

2.思路

    我也是学的官方思路:链接

    不懂最大公约数算法的先移步这里(gcd算法)

ac代码:

#include <stdio.h>

int gcd(int a,int b){
    if(b==0) return a;
    return gcd(b,a%b);
}

int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        int l,f,d;
        scanf("%d%d%d",&l,&f,&d);
        printf("%s\n",f<gcd(l,d)?"YES":"NO");
    }
    return 0;
}

hiho 232 拆字游戏

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

小Kui喜欢把别人的名字拆开来,比如“螺”就可以拆成“虫田糸”,小Kui的语文学的不是很好,于是她决定使用编程的方式来解决这个问题。

给出一个01矩阵,1占据的部分即为需要拆的字,如果两个1分享一条边,那么它们连通。连通具有传递性,即如果a、b连通,b、c连通,则a、c连通。

连通的一系列1被看做可以拆出的一块,现在小Kui需要输出这些拆出的块(用一个01矩阵表示,并且要求矩阵的大小尽可能的小)。

为了确保输出的顺序尽可能的和书写的顺序一致,小Kui从每个块中选出最左上角的点(最左侧的点中,最靠上的)作为代表点,然后按照代表点从左到右(若相同则按从上到下)的顺序输出所有拆出的块。

输入

输入的第一行为两个正整数N、M,表示01矩阵的大小。

接下来N行,每行M个01字符,描述一个需要拆的字。

对于40%的数据,满足1<=N,M<=10。

对于100%的数据,满足1<=N,M<=500。

输出

按照代表点从左到右(若相同则按从上到下)的顺序输出所有拆出的块。

对于每个块,先输出其大小,然后用对应的01矩阵表示这个块。

额外的样例

样例输入样例输出
11 17
00000000000000000
00001111111100000
00000000000000000111
001111111111111001
00000000100000000
00000010101110001
00000110100011000
00011100100001000
00000010100000000
00000001100000000
00000000000000000

7 13
1111111111111
0000001000000
0000001000000
0000001000000
0000001000000
0000001000000
0000011000000
3 4
0001
0011
1110
1 8
11111111
1 1
1
3 4
1110
0011
0001

样例输入

14 22
0000000000001111111100
0000000000001101101100
0000110000001111111100
0000110000001101101100
0111111110001111111100
0110110110000000000000
0110110110000011000000
0111111110001111111000
0000110000000001100000
0000110110001111111100
0111111111000111111000
0000000010001101101100
0000000000000001100000
0000000000000011100000

样例输出

10 9
000110000
000110000
111111110
110110110
110110110
111111110
000110000
000110110
111111111
000000010
5 8
11111111
11011011
11111111
11011011
11111111
8 8
00110000
11111110
00011000
11111111
01111110
11011011
00011000
00111000

题意分析:

1.题是什么?

    其实就是给你一个长n宽m的01矩阵,现在要你把这个图中连通的1视作一块拆分输出出来,连通是用的四连通规则.

2.思路

    做一个flag数组把像素图收录进去,然后二重循环依次判断flag[i][j]为1则从此点开始进行bfs,将此点及与此点连通的位置的flag值全部置为0,同时把经过的点记录到vector中,bfs的同时更新出此次dfs中经过的所有点的边界,此次bfs结束后,遍历vector把ans中对应的位置全部更新为1,输出对应边界内的ans,然后再次遍历vector把ans的所有位置更新为0并清空vector.继续寻找下一块

3.细节

    小心对于输入的处理,c语言中读单字符要自己处理换行符,我是用getchar()过滤的,

    其次一定要注意是按最左上方的1为准从左到右从上到下依次输出.

ac代码:

#include <iostream>
#include <vector>
#include <stdio.h>
using namespace std;
const int maxn=505;
int path[4][2]={1,0,0,-1,-1,0,0,1};//四方向数组 
char image[maxn][maxn];
char flag[maxn][maxn];
char ans[maxn][maxn];

vector<pair<int,int> > black;
int leftedge,rightedge,upedge,downedge;
void bfs(int x,int y,const int& n,const int& m){ //找出所有黑点存入black并更新出边界 
	flag[x][y]='0';
	upedge=downedge=x,leftedge=rightedge=y;
	black.clear();
	black.push_back(make_pair(x,y));
	for(int index=0;index<black.size();index++){
		for(int i=0;i<4;i++){
			int tx=black[index].first+path[i][0],ty=black[index].second+path[i][1];
			if(0<=tx&&tx<n&&0<=ty&&ty<m&&flag[tx][ty]=='1'){
				flag[tx][ty]='0';
				if(tx>downedge) downedge=tx;
				if(tx<upedge) upedge=tx;
				if(ty>rightedge) rightedge=ty;
				if(ty<leftedge) leftedge=ty; 
				black.push_back(make_pair(tx,ty));
			}
		}
	}
}
void solve(){
	int n,m; 
	scanf("%d%d",&n,&m);
	getchar();
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++) scanf("%c",&image[i][j]); 
		getchar();
	}
	
	//初始化,录入image到flag并初始ans画布 
	for(int i=0;i<n;i++) for(int j=0;j<m;j++){
		if(image[i][j]=='0') flag[i][j]='0';
		else if(image[i][j]=='1') flag[i][j]='1';
		ans[i][j]='0';
	}
	for(int j=0;j<m;j++) for(int i=0;i<n;i++){
		if(flag[i][j]=='1'){
			bfs(i,j,n,m);
			//渲染ans画布 
			for(int k=0;k<black.size();k++) ans[black[k].first][black[k].second]='1'; 
			//输出ans画布 
			printf("%d %d\n",downedge-upedge+1,rightedge-leftedge+1);
			for(int k=upedge;k<=downedge;k++){
				for(int l=leftedge;l<=rightedge;l++) printf("%c",ans[k][l]); 
				printf("\n");
			}
			//清空ans画布 
			for(int k=0;k<black.size();k++) ans[black[k].first][black[k].second]='0';
		}
	}
}

int main(){
	solve();
	return 0;
}

hiho 233 数组分拆

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

小Ho得到了一个数组作为他的新年礼物,他非常喜欢这个数组!

在仔细研究了几天之后,小Ho成功的将这个数组拆成了若干段,并且每段的和都不为0!

现在小Ho希望知道,这样的拆分方法一共有多少种?

两种拆分方法被视作不同,当且仅当数组断开的所有位置组成的集合不同。

输入

每组输入的第一行为一个正整数N,表示这个数组的长度

第二行为N个整数A1~AN,描述小Ho收到的这个数组

对于40%的数据,满足1<=N<=10

对于100%的数据,满足1<=N<=10^{5}, |Ai|<=100

输出

对于每组输入,输出一行Ans,表示拆分方案的数量除以(1e9+7)的余数。

样例输入

5
1 -1 0 2 -2

样例输出

5

题意分析:

1.题是什么?

    给你一个十万大小的数组,要你将之拆分为不同段,问每个段和都不为0的不同拆法总数(结果求余1e9+7)).

2.思路(参考自官方思路)

    首先一看到统计满足条件的情况总数自然该想到dp,问题是dp数组如何设计以及dp转移方程.

    我们这里设原数组为数组a,dp数组中dp[i]的意义是将a[0]~a[i-1]拆分的方法总数,则可以获得dp数组的转移方程如下:

        dp[i]=\sumdp[j] (0<=j<i && a[j+1]+a[j+2]+....+a[i]!=0)

    通过预处理a数组前缀和,关于a[j+1]+a[j+2]+....+a[i]!=0的判断可以以如下方式O(1)搞定:

    (定义一个a数组前缀和数组suma,suma[i]=\suma[j] (0<=j<=i),则若suma[i]!=suma[j],则a[j+1]+a[j+2]+....+a[i]!=0)

    可是由于求和的存在,这是一个O(n^{2})的dp,仍需优化思路.

    这里我们调整思路,定义一个dp数组的前缀和数组sumdp,sumdp[i]=\sumdp[j] (0<=j<=i),则状态转移方程可修改为

         dp[i]=sumdp[i-1]-\sumdp[j] (0<=j<i && suma[i]==suma[j])

    我们发现其实我们可以用一个map m把所有已计算出的dp[j]以suma[j]为键做一个统计,即m[x]=\sumdp[j] (0<=j<isuma[j]==x)

    则\sumdp[j] (0<=j<i && suma[i]==suma[j])的计算也可以以O(1)完成,故而如下优化为一个O(n)的dp.

        dp[i]=sumdp[i-1]-m[suma[i]]

    实际编写中关于dp,sumdp,suma由于只会用到上一个数据,故而我没用数组,只用的一个int,最大化节约空间.

3.小收获

    map中如果直接使用下标方式如m[1000]尝试访问键为1000的值,若map中存在则正常返回存的值,可是若不存在则视为一种插入操作,返回0的同时执行m.insert(make_pair(1000,0)),因此最好先检查键是否存在,否则以下标方式访问可能污染map.实验代码如下

#include <iostream>
#include <map>
using namespace std;

void solve(){
	map<int,int> m;
	m.insert(make_pair(0,1));
	cout<<"下标访问前的map:"<<endl; 
	for(map<int,int>::iterator ite=m.begin();ite!=m.end();ite++){
		cout<<ite->first<<" "<<ite->second<<endl;
	} 
	
	cout<<endl<<"键为1000的访问返回: "<<m[1000]<<endl;//下标访问 
	
	cout<<"下标访问后的map:"<<endl; 
	for(map<int,int>::iterator ite=m.begin();ite!=m.end();ite++){
		cout<<ite->first<<" "<<ite->second<<endl;
	} 
}

int main(){
	solve();
	return 0;
}

ac代码:

#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;

const int maxn=1e5+5; 
const int mod=1e9+7;
vector<int> a(maxn,0);

void solve(){
	int n;
	cin>>n;
	for(int i=0;i<n;i++) cin>>a[i];  
	
	unordered_map<int,int> m;
	m.insert(make_pair(0,1));
	int dp=1,suma=0,sumdp=1;
	for(int i=0;i<n;i++){
		suma+=a[i];
		dp=sumdp;
		if(m.count(suma)){
			dp=(dp-m[suma]+mod)%mod; 
			m[suma]=(m[suma]+dp)%mod;
		}
		else m[suma]=dp; 
		sumdp=(sumdp+dp)%mod;
	}
	cout<<dp<<endl;
}

int main(){
	solve();
	return 0;
}

hiho 234 矩形计数

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

如图所示,在由N行M列个单位正方形组成的矩形中,有K个单位正方形是黑色的,其余单位正方形是白色的。  

你能统计出一共有多少个不同的子矩形是完全由白色单位正方形组成的吗?

输入

第一行三个整数:N, M和K。

之后K行每行包括两个整数R和C,代表一个黑色单位正方形的位置。

1 <= N,M <= 1000  

1 <= K <= 10  

1 <= R <= N  

1 <= C <= M

输出

输出一个整数表示满足条件的子矩形个数。

样例输入

3 3 1
2 3 

样例输出

24

题目分析:

1.题是什么?

    n行m列单位正方形小块(1<=n,m<=10000),现告诉你其中的某k个小块是黑色的(k<=10),让你回答不含黑块的子矩形数目.

2.思路(参考自官方思路)

    依旧是满足某种条件的情况统计,不过由于不好设计dp的状态转移方程,卡了很久,在看过官方思路之后了解了一个之前一直在用却从未理论化的做法--容斥原理:把包含于某内容中的所有对象的数目先计算出来,然后再把计数时重复计算的数目排斥出去,使得计算的结果既无遗漏又无重复,这种计数的方法称为容斥原理。

            De Moivre (Abraham de Moivre)的容斥原理公式:   

    之所以说这道题用到了容斥原理是因为我们假设现在有三个已知位置的黑块A.B.C,则子矩形的所有情况如图:

    故而              不含黑块的子矩形数目=子矩形总数

                                                          -包含A的子矩形数目-包含B的子矩形数目-包含C的子矩形数目

                                                          +包含AB的子矩形数目+包含AC的子矩形数目+包含BC的子矩形数

                                                           -包含ABC的子矩形数目

故而这道其实是一个K量容斥问题,现在问题简化为:对于已知的黑块集合S,计算包含S的子矩形数目

如上图,首先我们明白唯一确定一个子矩形的方法是为其确定唯一的一组上下左右边界,

           而我们如何去判断某子矩形x是否包含黑块集合S呢?

           我们预处理出黑块集合S的上下左右边界,只要子矩形x的上下左右边界将黑块集合S的上下左右边界包含其中,那x自然也就包含了黑块集合S

           故而我们在预处理出黑块集合S的上下左右边界之后,将上下左右的剩余边界可取情况总数乘起来即为包含S的子矩形数目 

           例如图中左边界还有2种取法,右边界2种取法,上边界2种取法,下边界2种取法,故而2*2*2*2=16,即为包含S的子矩形数目

    我的代码中dfs所做的就是遍历所有可构造的黑块集合S,奇数情况加,偶数情况减这里体现的正是公式中的(-1)^{size(c)-1}

ac代码:

#include <iostream>
#include <stdio.h>
#include <vector>
using namespace std;
typedef long long ll; 
typedef pair<ll,ll> pa; 
const int maxk=10;
ll n,m,k;
pa black[maxk]; //所有黑块 
vector<pa> s;//黑块集合S
vector<pa>::iterator s_ite;

ll dfs(int num){ //以容斥原理公式计算所有包含各种黑块集合S的子矩形总数 
	if(num==k){ //若黑块集合S构造完成 
		if(s.size()){
			ll left=m+1,right=0,up=n+1,down=0;
			for(s_ite=s.begin();s_ite!=s.end();s_ite++){
				if(s_ite->second<left) left=s_ite->second;
				if(s_ite->second+1>right) right=s_ite->second+1;
				if(s_ite->first<up) up=s_ite->first;
				if(s_ite->first+1>down) down=s_ite->first+1;
			}
			ll sum=(left)*(m-right+2)*(up)*(n-down+2);//sum存的是含S的子矩形总数
			return s.size()%2?sum:-sum; //奇数加偶数减,来自公式 
		}
		return 0;
	}
	ll add=dfs(num+1);  //这个黑块不加到集合S中 
	s.push_back(black[num]);
	ll notadd=dfs(num+1);//这个黑块加到集合S中 
	s.pop_back();
	return add+notadd;
}

void solve(){
	scanf("%lld%lld%lld",&n,&m,&k);
	for(int i=0;i<k;i++) scanf("%lld%lld",&black[i].first,&black[i].second);
	ll ans=(n*(n+1)/2)*(m*(m+1)/2)-dfs(0);//注意一定要用longlong,因为1000^4爆了int 
	printf("%lld\n",ans);
}

int main(){
	solve();
	return 0;
}

hiho 235 闰秒

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

计算机系统中使用的UTC时间基于原子钟,这种计时方式同“地球自转一周是24小时”的计时方式有微小的偏差。为了弥补这种偏差,我们偶尔需要增加一个“闰秒”。  

最近的一次闰秒增加发生在UTC时间2016年的最后一天。我们在2016年12月31日23时59分59秒和2017年1月1日0时0分0秒之间增加了这样一秒:2016年12月31日23时59分60秒,记作2016-12-31 23:59:60。  

目前一共增加了27次闰秒,具体添加的时间见下表:

给出两个时间,请你判断在考虑闰秒的情况下,这两个时间间隔多少秒。  

输入

两个时间各占一行,格式是yyyy-MM-dd HH:mm:ss,范围在1970-01-01 00:00:00至2017-03-12 23:59:59之间。保证第一个时间不晚于第二个时间。

输出

两个时间间隔多少秒。

样例输入

2016-12-31 23:59:59 
2017-01-01 00:00:00

样例输出

2

题意分析:

1.题是什么?

    给你两个准确到秒的时刻,让你输出这两个时刻之间相差多少秒,要考虑闰秒与闰年.

2.思路

    自定义一个时间类,完成基础的配置后写一个parse方法计算与标准时间1970-01-01 00:00:00的秒差,两个时刻的秒差相减即为答案,关于闰秒的处理我是做出一个天生有序的闰秒时刻数组,独立计算闰秒的.

ac代码:

#include <iostream> 
#include <stdio.h>
#include <algorithm> 
#include <stdlib.h>
using namespace std;

bool isleapyear(int year){
	if((year%4==0&&year%100!=0)||(year%400==0)) return true;
	return false;
}
const int leapyear[12]={1972,1976,1980,1984,1988,1992,1996,2000,2004,2008,2012,2016};//题目时间范围内的闰年 
const int month_day[12]={31,28,31,30,31,30,31,31,30,31,30,31};//不考虑闰年,每月的天数 
const int qzh_month_day[13]={0,31,59,90,120,151,181,212,243,273,304,334,365};//month_day的前缀和 

class date{
	private:
		int year,month,day,hour,minute,second;
	public:
		date();
	    date(string tdate);
		int parse();
		friend ostream& operator<<(ostream& output,const date &D);
		friend istream& operator>>(istream& input,date &D);
		friend bool operator<(const date &a,const date &b){
			long long timea=(long long)a.year*1e10+(long long)a.month*1e8+a.day*1e6+a.hour*1e4+a.minute*1e2+a.second;
			long long timeb=(long long)b.year*1e10+(long long)b.month*1e8+b.day*1e6+b.hour*1e4+b.minute*1e2+b.second;
			return timea<timeb;
		}
};
date::date(){
	year=month=day=hour=minute=second=0;
}
date::date(string tdate){
	year=atoi(tdate.substr(0,4).c_str());
	month=atoi(tdate.substr(5,2).c_str());
	day=atoi(tdate.substr(8,2).c_str());
	hour=atoi(tdate.substr(11,2).c_str());
	minute=atoi(tdate.substr(14,2).c_str());
	second=atoi(tdate.substr(17,2).c_str());
}
const date leapseconds[27]={
			date("1972-06-30 23:59:60"),date("1972-12-31 23:59:60"),
			date("1973-12-31 23:59:60"),date("1974-12-31 23:59:60"),date("1975-12-31 23:59:60"),date("1976-12-31 23:59:60"),date("1977-12-31 23:59:60"),
			date("1978-12-31 23:59:60"),date("1979-12-31 23:59:60"),date("1981-06-30 23:59:60"),date("1982-06-30 23:59:60"),date("1983-06-30 23:59:60"),
			date("1985-06-30 23:59:60"),date("1987-12-31 23:59:60"),date("1989-12-31 23:59:60"),date("1990-12-31 23:59:60"),date("1992-06-30 23:59:60"),
			date("1993-06-30 23:59:60"),date("1994-06-30 23:59:60"),date("1995-12-31 23:59:60"),date("1997-06-30 23:59:60"),date("1998-12-31 23:59:60"),
			date("2005-12-31 23:59:60"),date("2008-12-31 23:59:60"),date("2012-06-30 23:59:60"),date("2015-06-30 23:59:60"),date("2016-12-31 23:59:60")};
int date::parse(){//返回此时间与1970-01-01 00:00:00的秒差
	int ans=0;
	//只考虑闰年的秒差
	ans+=(year-1970)*365*24*3600;
	for(int i=0;i<12;i++) if(leapyear[i]<year) ans+=24*3600;
    if(isleapyear(year)&&month>2) ans+=24*3600;
    
    ans+=qzh_month_day[month-1]*24*3600;
    ans+=(day-1)*24*3600;
    ans+=hour*3600+minute*60+second;
	//闰秒增加的秒差
	for(int i=0;i<27;i++) if(leapseconds[i]<*this) ans+=1;
	return ans; 
}
ostream& operator<<(ostream& output,const date &D){
	printf("%04d-%02d-%02d %02d:%02d:%02d",D.year,D.month,D.day,D.hour,D.minute,D.second);
	return output;
}
istream& operator>>(istream& input,date &D){
	scanf("%d-%d-%d %d:%d:%d",&D.year,&D.month,&D.day,&D.hour,&D.minute,&D.second);
	return input;
}


void solve(){
	date a,b;
	cin>>a>>b;
	cout<<b.parse()-a.parse()<<endl;
}

int main(){
	solve();
	return 0;
}

hiho 236 水陆距离

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

给定一个N x M的01矩阵,其中1表示陆地,0表示水域。对于每一个位置,求出它距离最近的水域的距离是多少。  

矩阵中每个位置与它上下左右相邻的格子距离为1。

输入

第一行包含两个整数,N和M。

以下N行每行M个0或者1,代表地图。

数据保证至少有1块水域。

对于30%的数据,1 <= N, M <= 100  

对于100%的数据,1 <= N, M <= 800

输出

输出N行,每行M个空格分隔的整数。每个整数表示该位置距离最近的水域的距离。

样例输入

4 4  
0110  
1111  
1111  
0110

样例输出

0 1 1 0  
1 2 2 1  
1 2 2 1  
0 1 1 0

题意分析:

1.题是什么?

    给你一个n行m列的矩阵,1陆地,0水域,要你计算出每个陆地距离最近的水域的距离,连通方式是四连通.

2.思路

    如果只有一个水域,那不就是单纯的Dijkstra模版题吗,这道其实就是多源最短路径问题,我的解决方法是以dijkstra思路进行优化,依旧是依靠一个队列进行向周边的扩散.

ac代码:

#include <iostream> 
#include <stdio.h>
#include <queue>
using namespace std;
typedef pair<int,int> pa;
const int maxn=802,maxm=802;
const int path[4][2]={1,0,0,-1,-1,0,0,1};//四方向数组 

char ma[maxn][maxm];
int dis[maxn][maxm];

void solve(){
	//获取输入 
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;i++) scanf("%s",&ma[i]);
	 
	//初始dis数组与queue
	queue<pa> q;
	for(int i=0;i<n;i++) for(int j=0;j<m;j++){
		if(ma[i][j]=='0'){
			dis[i][j]=0;
			q.push(make_pair(i,j));
		}
		else if(ma[i][j]=='1') dis[i][j]=-1;
	}
	
	//多源bfs
	while(!q.empty()){
		pa temp=q.front();
		q.pop();
		for(int i=0;i<4;i++){
			int tx=temp.first+path[i][0],ty=temp.second+path[i][1];
			if(tx>=0&&tx<n&&ty>=0&&ty<m&&dis[tx][ty]==-1){
				dis[tx][ty]=dis[temp.first][temp.second]+1;
				q.push(make_pair(tx,ty));
			}
		}
	}
	
	//输出dis
	for(int i=0;i<n;i++){
		printf("%d",dis[i][0]);
		for(int j=1;j<m;j++) printf(" %d",dis[i][j]); 
		printf("\n");
	} 
}

int main(){
	solve();
	return 0;
}

hiho 251 歌德巴赫猜想

时间限制:10000ms
单点时限:1000ms
内存限制:256MB

描述

哥德巴赫猜想认为“每一个大于2的偶数,都能表示成两个质数之和”。
给定一个大于2的偶数N,你能找到两个质数P和Q满足P<=Q并且P+Q=N吗?

输入

一个偶数N(4 <= N <= 1000000)

输出

    输出P和Q。如果有多组解,输出P最小的一组。

样例输入

10

样例输出

3 7

1.题是什么?

    从前有个很牛的数学家叫哥德巴赫,他猜想任意一个大于2的偶数都可以由小于它的两个质数相加得到,虽然他到死也没能证明,我们也到现在为止还没证明出来,不过我们可以放心的是这个猜想在int范围内是完全成立的,不信邪的可以运行以下代码跑跑试试

#include <stdio.h>

    回归正题,就是要你一个给定的1e6以内的偶数n,要你输出那两个和为n的质数a,b,有多种情况时只输出较小数最小的那一组数据。

2.思路   

    首先这道题素数埃式筛法是少不了的,不了解埃式筛法的先移步我的这个博客:素数处理-埃式筛法.

    现在我们利用埃式筛法筛选出n以内的所有素数于prime数组,然后遍历prime,通过flag实现O(1)检查n-prime[i]是否是素数,第一个满足条件的prime[i]与n-prime[i]自然就是答案

3.ac代码

#include <stdio.h>

// 位操作求素数 
const int maxn=1e6+1;
int prime[maxn],primenum;
int flag[maxn/32+1];//数组大小实际缩小8倍 

//预处理出t以内的所有素数及flag
void wei_prime(int t){
 primenum=0;
 flag[0]|=3;
 for(int k=1;k<=t/32+1;k++) flag[k]=0;
 for(int i=2;i<=t;i++){
        if(!((flag[i/32]>>(i%32))&1)){
            for(int j=i*2;j<=t;j+=i) flag[j/32]|=(1<<(j%32));
            prime[primenum++]=i;
        } 
    }
}

void solve(){
    int n,rest;
    scanf("%d",&n);
    wei_prime(n);
    for(int i=0;i<primenum;i++){
        rest=n-prime[i];
        if(!((flag[rest/32]>>(rest%32))&1)){         
            printf("%d %d\n",prime[i],rest);
            return;
         }
    }
}

int main(){
    solve();
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值