2018西南交通大学ACM新秀杯决赛题解与总结

 总结:
  本次比赛是我第一次参加的现场赛,之前只参加过一些网络赛,取得的名次倒还也不算太差。但在这次现场赛中,我却只做出两道题。究其原因:不知为何,相较于网络赛,这次比赛我十分慌张,不能静下来思考,做题时不能专注、集中精神;其次,编译器的不顺手更让我紧张加剧。但这些其实都不该是理由,最主要的理由应该还是我的实力不够,还需要更多的积累、练习。
  下来之后重新做了比赛时的题目,发现其实赛题难度不大,几乎没有考察算法,如果能冷静下来认真做的话,至少有7道题是没问题的。
  接下来我们一道一道解决。
  
Problems
A Key
B 吕元数猜想
C 人类的本质
D A%B problem
E 黑珍珠号的诅咒(The Curse of the Black Pearl)
F 聚魂棺(Dead man’s Chest)
G 喵星网络赛
H N桥问题
I Array

                    Problem A. Key
Time limit: 1000 ms
Memory limit: 256 MB
Input file: standard input
Output file: standard output

庶神有 n 个不同的锁和对应的钥匙。现在他把钥匙打乱了,庶神有一个怪癖,他会把钥匙一个一个插进锁里再拧开。求恰好打开一把锁的情况数。
Input
输入包含多组测试数据,第一行为一个正整数 ? (0≤?≤105) 表示有 T 组测试数据。
对于每组则测试数据,
仅有一行包含一个正整数 ? (1≤?≤107) ,代表有 n 对钥匙和锁。
Output
对于每组测试数据,
输出一行包含一个整数,表示恰好打开一把锁的情况数。答案可能会很大,请将结果对 107+7 取模

 解题思路:
  此题是一道简单的错排问题。要求恰好打开一把锁的情况数,则先确定打开的锁:总共有n种情况。然后对剩下的n-1把锁进行错排:用f[i]表示i把锁错排的情况数,由递归的思想很容易得f[i]=(i-1)(f[i-1]+f[i-2])。最后的答案就为nf[n-1]。
  处理细节上将错排数提前预处理即可。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
const int BASE=1e7+7,MAXN=1e7;
long long dp[MAXN+5];					//dp[i]:i个数错排的情况数 
int main(void){
	int T;scanf("%d",&T);
	dp[0]=1,dp[1]=0;
	for(int i=2;i<=MAXN;++i)
		dp[i]=((i-1)*(dp[i-1]+dp[i-2]))%BASE;
	int n;
	while(T--){
		scanf("%d",&n);
		printf("%lld\n",(n*dp[n-1])%BASE);
	}
	return 0;
}

 本题小结:
 此题单纯考察了错排,几乎没有变形,之前练习时遇到过同样类型甚至包括更多变形的题目,但是这次现场赛时却没有做出来这道题???
 或许是第一次参加现场赛比较慌张的缘故,但也绝对不该丢掉此题。此题同样暴露出了我在 组合数学 方面存在严重问题,需重视。

 
 

                   Problem B. 吕元数猜想
Time limit: 1000 ms
Memory limit: 128 MB
Input file: standard input
Output file: standard output

给定两个正整数 A 和 B
要求找出所有的整数对 (C, D)
满足 ?≤?≤?, ?≤?≤? 且 AB+BA≤CD+?? (分数运算,并非整除)
Input
输入包含多组测试数据,
第一行为一个正整数 ? (0≤?≤20) 表示有 T 组测试数据。
对于每组则测试数据,
仅有一行包含两个整数 ? (0<?≤109) 和 ? (0<?≤109) 含义如题目描述所示
Output
对于每组测试数据,
第一行包含一个整数 X 表示所求整数对的数量,
接下来 X 行,每行包含一个所求整数对 (C,D)
将答案按 C 的大小从小到大进行排序后输出,若 C 大小相等,则 C 相等的整数对按 C 的大小从小到大排序。如 (3,4)<(3,5) , (3,4)>(2,4)

 解题思路:
  由题设条件易得,C<=D和D<=C是一组对称的结果,所以我们假设C<=D。
  仔细观察便能得到不等式两端有着相同的形式:f(x)=x+1/x。
  根据A/B<=C/D<=1<=D/C<=B/A,因此有f(D/C)=f(C/D)<=f(A/B)=f(B/A),当且仅当A/B=C/D,即A=C,B=D时等号成立。
  再由不等式得等号成立所以A=C,B=D。再加上D<=C的情况即为全部答案。

#include <stdio.h>
int main(void){
	int T,a,b;scanf("%d",&T);
	while(T--){
		scanf("%d%d",&a,&b);
		if(a==b) printf("1\n%d %d\n",a,a);
		else printf("2\n%d %d\n%d %d\n",a,b,b,a);
	}
	return 0;
}

 本题小结:
 本题是这次比赛唯二做出的题中的一道,并且实际做题时思路也没有上述那么严谨,更多的是偏向于猜测:同样假设C<=D,得到了A/B<=C/D<=1<=D/C<=B/A。
 再看给出的不等式,移项有:B/A-D/C<=C/D-A/B。可以看出右式中两个数都小于等于1,左式两个数都大于等于1,则要想使不等号成立,需两大于1的数的差值小于两小于1的数的差值,显然缩小左式的值较右式更为困难,因此猜测不等号最多只有等号成立。
 这道“难得”做对的题还是猜对的,让我在比赛时又受了次打击。

 
 

                      Problem C. 人类的本质
Time limit: 1000 ms
Memory limit: 256 MB
Input file: standard input
Output file: standard output

众所周知,在某些比赛交流群里,总是能完美地体现人类的本质,这是漫长的看算法做算法题生涯中为数不多的乐趣之一。这本来无可厚非,但是在 SWJTU 比赛交流群里,这种行为严重影响了群主的日常生活,他不得不停下他的家长会来答疑,当他发现群成员并没有问题而只是一些复读消息时,他生气了。于是群里出现了复读禁言机器人。
复读禁言机器人总是禁言最后一条复读消息的发送者。有些成员发现了这个现象但还是打算皮一下,所以他们在发出消息的一段时间后又撤回了他们的消息。复读禁言机器人并不是时时刻刻在线的(因为机器人也是有妹子的),所以在机器人禁言的总是目前屏幕上剩下的消息中,最后一条复读消息的发送者。
已经被禁言的成员是不会再复读并且不能再被禁言的。数据保证每次操作的合理性。
Input
输入只包含一组测试数据,
第一行为一个正整数 n 和 q (1≤?≤?≤105) ,表示 n 个人参与复读,今天有 k 次操作(有些成员可能不遵循复读基本法,他们复读了不止一次)。
接下来有 q 行,每行包含一个整数 ? (1≤?≤?) 和一个字符串 s ( s 仅包含小写字母且长度不超过 10 )表示操作种类和执行操作的群成员的名字。
?=0 表示群成员 s 撤回了他最近的一条复读消息。
?=1 表示群成员 s 复读了一条消息。
?=2 表示禁言机器人 s 禁言了一个成员。
Output
输出包含若干行,
每行包含一个字符串,表示对于每次 ?=2 时,被禁言成员的名字。

 解题思路:
  很容易想到用栈记录复读序列,同时“撤回一次”表示抵消一次禁言,那么“禁言一次”就可以表示抵消无穷次禁言。注意将成员的名字用整数表示。

#include <iostream>
#include <string>
#include <algorithm>
#include <map>
#include <vector>
#include <stack>
using namespace std;
const int MAXN=100005;
map<string,int> ID;
int n,q;
int wd[MAXN];							// 撤回次数 
vector<string> name;
stack<int> s;
int main(void){
	cin>>n>>q;
	string str;
	for(int i=0,d;i<q;++i){
		cin>>d>>str;
		if(d==1){
			if(ID.find(str)==ID.end()){
				ID[str]=name.size();
				name.push_back(str);
			}
			s.push(ID[str]);
		}
		else if(d==0) ++wd[ID[str]];
		else if(d==2){
			int id;
			while(wd[(id=s.top())]){
				--wd[id];s.pop();
			}
			wd[id]=MAXN;s.pop();
			cout<<name[id]<<endl;
		}
	}
	return 0;
}

 本题小结:
 代码AC不了,但实在找不到问题。

 
 

                     Problem D. A%B problem
Time limit: 1000 ms
Memory limit: 128 MB
Input file: standard input
Output file: standard output

这道题是众所周知的真·签到题 A+B Problem 的进阶版,只不过加变成了取模。听起来挺简单的,那来做做吧!
Input
输入包含多组测试数据,第一行为一个正整数 ? (1≤?≤2333) 表示有 T 组测试数据。
对于每组则测试数据,
仅有一行包含两个整数 ? (1≤?≤10233) 和 ? (1≤?≤2×109)。
Output
对于每组测试数据,
输出一行包含一个整数,即 A 除以 B 的余数。

 解题思路:纯模板题。

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
const int L=300;  
string s1,s2;
int sub(int *a,int *b,int La,int Lb)  
{  
    if(La<Lb) return -1;//如果a小于b,则返回-1  
    if(La==Lb)  
    {  
        for(int i=La-1;i>=0;i--)  
            if(a[i]>b[i]) break;  
            else if(a[i]<b[i]) return -1;//如果a小于b,则返回-1  
  
    }  
    for(int i=0;i<La;i++)//高精度减法  
    {  
        a[i]-=b[i];  
        if(a[i]<0) a[i]+=10,a[i+1]--;  
    }  
    for(int i=La-1;i>=0;i--)  
        if(a[i]) return i+1;//返回差的位数  
    return 0;//返回差的位数  
  
}  
string div(string n1,string n2,int nn)//n1,n2是字符串表示的被除数,除数,nn是选择返回商还是余数  
{  
    string s,v;//s存商,v存余数  
     int a[L],b[L],r[L],La=n1.size(),Lb=n2.size(),i,tp=La;//a,b是整形数组表示被除数,除数,tp保存被除数的长度  
     fill(a,a+L,0);fill(b,b+L,0);fill(r,r+L,0);//数组元素都置为0  
     for(i=La-1;i>=0;i--) a[La-1-i]=n1[i]-'0';  
     for(i=Lb-1;i>=0;i--) b[Lb-1-i]=n2[i]-'0';  
     if(La<Lb || (La==Lb && n1<n2)) {  
            //cout<<0<<endl;  
     return n1;}//如果a<b,则商为0,余数为被除数  
     int t=La-Lb;//除被数和除数的位数之差  
     for(int i=La-1;i>=0;i--)//将除数扩大10^t倍  
        if(i>=t) b[i]=b[i-t];  
        else b[i]=0;  
     Lb=La;  
     for(int j=0;j<=t;j++)  
     {  
         int temp;  
         while((temp=sub(a,b+j,La,Lb-j))>=0)//如果被除数比除数大继续减  
         {  
             La=temp;  
             r[t-j]++;  
         }  
     }  
     for(i=0;i<L-10;i++) r[i+1]+=r[i]/10,r[i]%=10;//统一处理进位  
     while(!r[i]) i--;//将整形数组表示的商转化成字符串表示的  
     while(i>=0) s+=r[i--]+'0';  
     //cout<<s<<endl;  
     i=tp;  
     while(!a[i]) i--;//将整形数组表示的余数转化成字符串表示的</span>  
     while(i>=0) v+=a[i--]+'0';  
     if(v.empty()) v="0";  
     //cout<<v<<endl;  
     if(nn==1) return s;  
     if(nn==2) return v;  
} 
int main(void){
	int T;cin>>T;
	while(T--){
		cin>>s1>>s2;
		cout<<div(s1,s2,2)<<endl;
	}
	return 0;
}

 本题小结:
 本题提醒我比赛一定要记得带模板。

 
 

                    Problem E. 黑珍珠号的诅咒(The Curse of the Black Pearl)
Time limit: 1000 ms
Memory limit: 128 MB
Input file: standard input
Output file: standard output

故事发生在传说中海盗最活跃的 加勒比海(Caribbean Sea)。这片神秘的海域位于北美洲东南部,那里碧海蓝天,阳光明媚,海面水晶般清澈透明。
玩世不恭的 Jack Sparrow (Oh, I mean, Captain Jack Sparrow …) 是活跃在加勒比海上的年轻海盗,拥有令人闻风丧胆的 “黑珍珠号”(Black Pearl) 海盗船。对他来说,最惬意的生活莫过于驾驶着 “黑珍珠号” 在加勒比海上游荡,自由自在地打劫过往船只。但不幸的是,他遭到了自己的大副 Barbossa 的背叛,不仅自己珍爱的 “黑珍珠号” 被 Barbossa 偷走,自己也被遗弃在荒岛独自等死。
与此同时,抢劫了 “黑珍珠号” 的 Barbossa 变得更加猖狂,在加勒比海上横行霸道,一时成为整个加勒比海的霸主。但 Barbossa 和他的船员们却因掠取了 “阿兹特克金币”(Aztec Gold) 而遭到了邪恶的诅咒:成为无法满足一切欲望的骷髅,他们渴得要死却渴不死,他们饿得要死却饿不死,他们感不到海风拂面、他们触不到浪花拍打……
终于, Barbossa 得知,只有将 882 枚散落在世界各地的 “阿兹特克金币” 集齐,才能够消除诅咒,于是他派遣 Marty 去四处搜集散落的金币。
一天, Marty 在集市上看到了 n 枚闪闪发光的金币, Marty 并不知道这是不是可以解除他们诅咒的金币,所以 Marty 打算买一些回去碰碰运气。
这些金币被串成了一串,每枚金币都有各自的价格。众所周知, Marty 是一个丢三落四的家伙,他并不能带着散装的金币回去。也就是说,如果他打算购买金币的话,就只能买连续的、连成一串的若干枚。而在那个时候,扫码转账等移动支付还并没有普及开来,所以 Marty 只能用口袋里无数个价值为 a 的钱币,去换取疑似的 “阿兹特克金币” 。
在 Marty 打算交钱的时候,得知店主并没有零钱找给他。由于 Marty 还是一个极其小气的人,如果没有找零, Marty 是不可能用高价值的钱币去换取小价格金币的,即使那些金币有可能是去除诅咒的关键。这就表示, Marty 买的某串金币价格的总和必定是 k 的倍数。
The 14th ACM-ICPC Southwest Jiaotong University Programming Contest Freshman Cup
Southwest Jiaotong University, Sunday December 9, 2018
Page 10 of 18
但 Marty 的数学能力也很差……他并不知道他能否找到某串合适的金币,所以他把这个问题交给了你……
Input
输入包含多组测试数据,第一行为一个正整数 ? (1≤?≤10) 表示有 T 组测试数据。
对于每组测试数据,
第一行包含两个正整数 ? (1≤?≤105) 和 ? (1≤?≤5×104) ,表示集市有 n 枚金币, Marty 有无数个价值为 k 的钱币。
第二行包含 n 个正整数 ?? (0≤??≤109) ,表示那 n 个连在一起的金币各自的价格。
Output
对于每组测试数据,
输出一行包含一个字符串表示结果,如果 Marty 可以买到某串金币,则输出 “YES” (不包含引号);否则输出 “NO” (不包含引号)。

 解题思路:
  本题可以看做判断序列中是否存在和为k的倍数的子串,直接暴力求解的话O(n^2)可能会超时,因此可使用分治的策略。
  将一个序列分为左右两部分,一个序列符合要求代表左串(1)、右串(2)或者从序列中间跨过左右两边的子串(3)符合要求。降低复杂度主要考虑对(3)的处理,由于只需要判断子串的和是否为k的倍数,即为(3)中左半边的和加上右半边的和为k的倍数,即对左半边每个和值查找在右半边中是否存在对应的值满足条件,因此我们可以考虑用STL中的红黑树来实现,复杂度f(n)=O(nlogn)。
  写出时间复杂度的递推公式:
   T(n)=2T(n/2)+O(nlogn),由主定理得:T(n)=O(nlogn)。
   (需要注意的是,虽然理论值如此,但是实际中STL的运行速度很慢)

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <algorithm>
#include <set>
using namespace std;
int n,k,a[100005];				//a[i]从1到n储存	
int sum[100005];				//从1到i的子串和(mod k)
set<int> s;
bool Can(int i,int j){
	if(i==j) return a[i]%k==0;
	else if(j-i==1) return (a[i]+a[j])%k==0;
	int mid=(i+j)/2;s.clear();
	for(int p=mid+1;p<=j;++p)
		s.insert((sum[p]-sum[mid]+k)%k);
	for(int p=mid,x;p>=i;--p){
		x=(sum[mid]-sum[p-1]+k)%k;
		if(s.find(k-x)!=s.end())
			return true;
	}
	return Can(i,mid)||Can(mid+1,j);
}
int main(void){
	int T;scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&k);
		bool flag=false;
		for(int i=1;i<=n;++i){
			scanf("%d",&a[i]);
			if(a[i]==0) --i,--n;
			else if(a[i]%k==0) flag=true;
			else a[i]%=k;
		}
		if(n==0){
			puts("NO");continue;
		}
		for(int i=1;i<=n;++i){
			sum[i]=(sum[i-1]+a[i])%k;
			if(sum[i]==0){
				flag=true;
				break;
			}
		}
		if(flag==true||Can(1,n))
			puts("YES");
		else puts("NO");
	}
	return 0;
}

 本题小结:
 本题稍微考察了一点分治的算法,难度不大。实际上本题数据量不算大,比赛时用了O(n^2)的暴力方法加上一定的剪枝也过了。

 
 


                     Problem F. 聚魂棺(Dead man’s Chest)
Time limit: 1000 ms
Memory limit: 128 MB
Input file: standard input
Output file: standard output

Davy Jones 曾是英俊的北海海盗王,驾驶着 “飞翔的荷兰人号” (the Flying Dutchman) 在海上驰骋。和其他海盗一样, Davy Jones 也有着对冒险的热衷,有着对大海的痴狂,如果没有遇到那个她,他会和 Jack , Barbossa 一样,杀人,防火,抢劫,在 Tortugar 一手拿着朗姆酒瓶,一手拿着枪开怀地大笑……
但他偏偏遇到了,他遇到了那个她,海神 Calypso 。从此 Jones 像着了魔一样深深爱上了 Calypso 。也许 Calypso 只是玩弄,也许是真的也喜欢上了 Jones , Calypso 提出让 Jones 帮忙,替她照顾那些在海上死去的亡灵,将他们引渡到阴间。这个任务要在海上航行十年,才能有上岸的一天,与爱人 Calypso 相见。
为了深爱的人, Jones 答应了:他不再逍遥四海,不再烧杀掳掠,他放下杀戮、放弃自由,为了爱人他放弃了海盗的尊严。从此,纵情高歌他不参与,纷繁世俗与他无关,有的只是一个人,一条船,做一个引渡者,带着亡魂来往于阴间与人世。为了爱情,海盗也变得靠谱了起来。就这样,一天,两天、一年,又一年……十年虽长,但 Jones 不急,为了自己的深爱的人,他可以等,他认为这所有的一切是值得的……
然而十年终了,当 Jones 上岸来到他与 Calypso 相约的地方时,等待他的,却只有一席空地…… Jones 委屈,愤怒,不解,失望……黄昏将至,即将到来的又是一个漫长的十年。 Jones 的内心太过痛苦,他想结束这一切,他将自己的心脏挖出,连同情书一起锁进箱子里,藏在遥远的地方,他想发誓忘记 Calypso ,但他做不到…… 他放弃了作为引渡者的工作,并在大海上肆意杀戮,以掩盖自己心里的痛苦……
被他抓到的水手,只有两条路: 要么丧身大海,没有了引渡人,那就只能永远的做海上亡魂;要么成为 “飞翔的荷兰人号” 船员,在船上服役 100 年,届时,必定思想麻木、肉体腐败,船员的一部分也将成为船体的一部分。
一天,船员 系带王 Bill 因一个新来的家伙而和船长 Jones 赌博起来。赌的方法是扑克。不同于我们平常 13 种数字牌的扑克 {A,2,3,…,10,J,Q,K} ,这种扑克一共有 n 种数字 {1,2,3,…,n} 。
为了简化问题,我们约定每个人只能出 “对子” (即两张数字一样的牌) 和 “顺子” (即三张数字相连的牌) 。
现已知 Bill 的所有牌,请问他最多可以出牌几次。
Input
输入包含多组测试数据,第一行为一个正整数 ? (1≤?≤10) 表示有 T 组测试数据。
对于每组测试数据,
第一行包含一个正整数 ? (1≤?≤105) 表示扑克有几种数字。
接下来一行包含 n 个正整数 ?? (0≤??≤109),表示 Bill 有 ?? 张数字为的 i 的扑克。
Output
对于每组测试数据,
输出一行包含一个字符串表示结果,如果 Marty 可以买到某串金币,则输出 “YES” (不包含引号);否则输出 “NO” (不包含引号)。

 解题思路:
  本题可运用贪心的思想。将出“对子”称为操作1,“顺子”为操作2。不难发现,除了11211这种情况外,优先操作1必定不会有亏损。因此当某种牌的数量大于2时,优先进行操作2,这样最后我们能得到一个最大值为2的序列。
  然后我们便要排除11211此种情况。我们又能想到当出现“11*”此种情况时,优先操作2也必定不会有亏损,且对后续操作无后效性。这样我们便得到了贪心的策略:先对所有数量大于2的牌进行操作1,得到一个数量最大为2的序列,然后对序列顺序处理,出现“11*”执行操作2,其余执行操作1。

#include <stdio.h>
#include <stdlib.h>
long long ans;
int a[100005],N;
int main(void){
	int T;scanf("%d",&T);
	while(T--){
		scanf("%d",&N);
		ans=0;
		for(int i=0;i<N;++i){
			scanf("%d",&a[i]);
			if(a[i]){
				if(a[i]%2==0) ans+=(a[i]-2)/2,a[i]=2;
				else ans+=(a[i]-1)/2,a[i]=1;
			}
		}
		a[N]=a[N+1]=a[N+2]=0;
		for(int i=0,x=a[0],y=a[1],z=a[2];
			i<N;++i)
				if(x==1&&y==1&&z)
					++ans,x=y-1,y=z-1,z=a[i+3];
				else{
					if(x==2) ++ans;
					x=y,y=z,z=a[i+3];
				}
		printf("%lld\n",ans);
	}
	return 0;
}

 本题小结:
 比赛中看到本题我首先想到的是用dp,但是却找不到合适的状态,因此放弃了。比赛结束后突然想到本题好像可以贪心,于是尝试了一下,但是我才疏学浅,没有想到如何用数学方法严格地证明贪心的结论,以后有空会再尽力尝试一下。

 
 

                   Problem G. 喵星网络赛
Time limit: 1000 ms
Memory limit: 256 MB
Input file: standard input
Output file: standard output

在遥远的喵星文明中,一天被划分为 n 小时。
因此,喵星被划分为 n 个时区, 相邻时区的时差为 1 小时。也就是说,当第 1 个时区的时间为 1:00 时,第 2 个时区的时间就为 2:00 , …… , 第 n 个时区的时间为 n:00 。
对于每个时区的喵星人来说,他们只会在当地的 2 点到 F 点之间活动(即从 S:00 开始, F:00 结束)。
喵星打算举办一场时长 1 小时的程序设计网络赛,从整点开始,已知第 i 个时区有 ?? 只喵星人,请问什么时候开始,能够参加比赛的喵星人的数量最多? 输出答案为第 1 个时区的当地时间。
Input
输入包含多组测试数据,第一行为一个正整数 ? (1≤?≤30) 表示有 T 组测试数据。
对于每组则测试数据,
第一行为一个正整数 ? (2≤?≤105) ,代表喵星一天有 n 个小时,被划分为 n 个时区;
第二行有 n 个数, 第 i 个数为 ?? (1≤??≤104) ,代表第 i 个时区有 ?? 只喵星人。
第三行输出两个整数 S 和 F (1≤?≤?≤?) , 代表每个时区喵星人的活动时间。
Output
对于每个测试数据,
输出一行包含一个整数,表示在第 1 时区下,使能够参加比赛的喵星人数最多的比赛开始的整点时间。
0:00 用 n:00 表示。

 解题思路:
  本题可先将每个时区的时间都转换为第1个时区的时间,转换公式为:第i个时区的t点等于第一个时区的(t-i+n)%n+1点。然后将每个时区的活动时间转换为第一个时区的时间,得到S[i]和F[i],便容易看出此题是一个区间叠加类的问题。
  因为活动时间的区间都是连续的,所以可用f[i]表示从S[i]开始活动的总人数。令φ=S-F-1,则f[i]=sum(a[i],a[i+φ]),S>F。当f[i]最大时对应的S[i]即为答案。
  当S=F时,去答案为1。
  处理sum时,可用一维数组sum[i]表示从第1个时区到第i个时区的人数和,节省时间空间。
  复杂度O(N)。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int a[100005],f[100005];
int	S,F,n,sum[100005];
int main(void){
	int T;scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		for(int i=1;i<=n;++i) scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
		scanf("%d%d",&S,&F);
		if(S==F) puts("1");
		else{
			int t=F-S-1;
			for(int i=1;i<=n;++i)
				if(i+t<=n) f[i]=sum[i+t]-sum[i-1];
				else
					f[i]=sum[(i+t+n-1)%n+1]+sum[n]-sum[i-1];
			int p,m=0;
			for(int i=1;i<=n;++i)
				if(f[i]>m) p=i,m=f[i];				
			printf("%d\n",(S-p+n)%n+1);			
		}
	}
	return 0;
}

 本题小结:
 本题若说难度,主要就是题目读起来比较绕,比赛时读着读着就晕了,根本没有好好思考过。不过本题理解了之后做并不复杂。

 
 

                    Problem H. N桥问题
Time limit: 1000 ms
Memory limit: 128 MB
Input file: standard input
Output file: standard output

X 开车来到了一条河边,河上有 n 座互相平行的桥。 X 想按照指定的顺序通过所有的桥仅一次,但是又不想让陆上的车辙印交叉(过第一个桥前的车辙不算)。假设陆地平坦,面积无限大,车的大小和形状不计。 X 想知道对于给定的过桥顺序,他能否达成这样的目标。
Input
输入包含多组测试数据,第一行为一个正整数 ? (1≤?≤10) 表示有 T 组测试数据。
对于每组则测试数据,
第一行包含一个正整数 ? (1≤?≤106) 表示桥的数量,
第二行包含 n 个互不相同的整数 ?? (1≤??≤?), 表示第 i 个桥需要第 ?? 个通过。
数据保证所有 n 的和不超过 106。
Output
对于每个测试数据,
输出一行表示答案,如果 X 能按照这样的次序通过,输出 “Yes” (不包含引号),否则输出 “No” (不包含引号)。
 解题思路:
  咳咳,,,,,还没做出来。

 
 

                    Problem I. Array
Time limit: 1000 ms
Memory limit: 128 MB
Input file: standard input
Output file: standard output

一个数列 A ,长度为 n ,编号从左至右为 1…n ,为了满足某种特定的规律, ?? 不超过 k ,且大于 1 。
现在有两种操作。
1、代表将整个数列的值加 1 ,即 ?? = ??%?+1 。
2、查询区间 [?,?] 中值为 X 的个数。
Input
输入包含多组测试数据,第一行为一个正整数 ? (1≤?≤5) 表示有 T 组测试数据。
对于每组则测试数据,
第一行三个数 ? (1≤?≤105), ? (1≤?≤20) 和 ? (1≤?≤105) ,代表数组长度,值的上限和询问的次数。
第二行包含 n 个数代表 ?? 的值。
接下来有 q 行有 q 次操作。
1 代表 操作1 。
2 l r x 代表 操作2 。
Output
对于每次询问输出所求值。

 解题思路:
  本题考察内容也较为简单,主要考察计算数组中某一区间某数的个数。为了节省时间和空间可用一个二维数组sum[i][k]表示从i到N区间中k的个数。对任一区间[i,j],num(k)=sum[i][k]-sum[j+1][k]。k最大为20,因此复杂度O(N)。
  需要注意的地方是对操作1的记录:只需要记录操作1的次数t,最后查询时的原始值为(x-t+k-1)%k+1。

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
using namespace std;
int k,q,N,a[100005];
int sum[100005][21];		//sum[i][j]= <从0到i数组中j的个数> 
int main(void){
	int T;scanf("%d",&T);
	while(T--){
		scanf("%d%d%d",&N,&k,&q);
		for(int i=0;i<=k;++i) sum[N][i]=0;
		for(int i=0;i<N;++i) scanf("%d",&a[i]);
		for(int i=N-1;i>=0;--i)
			for(int j=1;j<=k;++j)
				if(a[i]!=j)	sum[i][j]=sum[i+1][j];
				else sum[i][j]=sum[i+1][j]+1;
		
		int num=0;			//操作1的次数
		for(int i=0,op;i<q;++i){
			scanf("%d",&op);
			if(op==1)	++num;
			else{
				int l,r,x;
				scanf("%d%d%d",&l,&r,&x);
				x=((x-(num%k)+k-1)%k)+1;
				printf("%d\n",sum[l-1][x]-sum[r][x]);
			}
		}
	}
	return 0;
}	

 本题小结:
 本题没有什么难点,思路也很自然。但是在比赛时不知道为什么,处理sum数组时用了顺序处理:复杂度O(N^2)???所以妥妥的TLE。可能果真是太紧张了,做题时完全没有进行思考。需要好好反思。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值