【未完成0.0】Noip2012提高组day2 解题报告

  第一次写一套题的解题报告,感觉会比较长。(更新中Loading....):)


题目:

第一题:同余方程

描述

求关于x的同余方程ax ≡ 1 (mod b)的最小正整数解。

格式

输入格式

输入只有一行,包含两个正整数a, b,用一个空格隔开。

输出格式

输出只有一行,包含一个正整数x0,即最小正整数解。输入数据保证一定有解。

样例1

样例输入1

 
3 10

样例输出1

 
7

限制

每个测试点1s

提示

对于40%的数据,2 ≤b≤ 1,000; 
对于60%的数据,2 ≤b≤ 50,000,000; 
对于100%的数据,2 ≤a, b≤ 2,000,000,000。


第二题:借教室

描述

在大学期间,经常需要租借教室。大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室。教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样。

面对海量租借教室的信息,我们自然希望编程解决这个问题。我们需要处理接下来n天的借教室信息,其中第i天学校有ri个教室可供租借。共有m份订单,每份订单用三个正整数描述,分别为dj,sj,tj,表示某租借者需要从第sj天到第tj天租借教室(包括第sj天和第tj天),每天需要租借dj个教室。 
我们假定,租借者对教室的大小、地点没有要求。即对于每份订单,我们只需要每天提供dj个教室,而它们具体是哪些教室,每天是否是相同的教室则不用考虑。

借教室的原则是先到先得,也就是说我们要按照订单的先后顺序依次为每份订单分配教室。如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配,通知当前申请人修改订单。这里的无法满足指从第sj天到第tj天中有至少一天剩余的教室数量不足dj个。

现在我们需要知道,是否会有订单无法完全满足。如果有,需要通知哪一个申请人修改订单。

格式

输入格式

第一行包含两个正整数n,m,表示天数和订单的数量。 
第二行包含n个正整数,其中第i个数为ri,表示第i天可用于租借的教室数量。 
接下来有m行,每行包含三个正整数dj,sj,tj,表示租借的数量,租借开始、结束分别在第几天。 
每行相邻的两个数之间均用一个空格隔开。天数与订单均用从1开始的整数编号。

输出格式

如果所有订单均可满足,则输出只有一行,包含一个整数0。否则(订单无法完全满足)输出两行,第一行输出一个负整数-1,第二行输出需要修改订单的申请人编号。

样例1

样例输入1

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

样例输出1

 
-1
2

限制

每个测试点1s

提示

对于10%的数据,有1≤ n,m≤ 10; 
对于30%的数据,有1≤ n,m≤1000; 
对于70%的数据,有1≤ n,m≤ 10^5; 
对于100%的数据,有1≤n,m≤10^6,0≤ri,dj≤10^9,1≤sj≤tj≤n。


第三题:疫情控制

描述

H国有n个城市,这n个城市用n-1条双向道路相互连通构成一棵树,1号城市是首都,也是树中的根节点。 
H国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,首都是不能建立检查点的。 
现在,在H国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。 
请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。

格式

输入格式

第一行一个整数n,表示城市个数。 
接下来的n-1行,每行3个整数,u、v、w,每两个整数之间用一个空格隔开,表示从城市u到城市v有一条长为w的道路。数据保证输入的是一棵树,且根节点编号为1。 
接下来一行一个整数m,表示军队个数。 
接下来一行m个整数,每两个整数之间用一个空格隔开,分别表示这m个军队所驻扎的城市的编号。

输出格式

共一行,包含一个整数,表示控制疫情所需要的最少时间。如果无法控制疫情则输出-1。

样例1

样例输入1

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

样例输出1

 
3

限制

每个测试点2s

提示

保证军队不会驻扎在首都。 
对于20%的数据,2≤ n≤ 10; 
对于40%的数据,2 ≤n≤50,0<w <10^5; 
对于60%的数据,2 ≤ n≤1000,0<w <10^6; 
对于80%的数据,2 ≤ n≤10,000; 
对于100%的数据,2≤m≤n≤50,000,0<w <10^9。

解题报告:

  先扯一点题外话吧。现在是暑假集训(shang ke)的倒数第二天,明天就要回家了(呼...),16天的集训,也测试了9次,9次(0.0)好像明天还有一场测试。(表示好心累)突然想起hzwer博客的那句话“自己选的路,跪着也要走完”,现在正是需要这种精神。这个暑假,好好奋斗吧。

  好的,废话就少说了。但其实今天的任务还没有做完,最后一题的长程序还没有编完,主要是今天有点浮躁,静不下心(毕竟明天就回家了),但是一定要编,学习一下上一届学长的刻苦。啊,又说了一大堆废话。-.-

  好的,开始了。

  首先第一题,同余方程。前两天才讲的数论,今天就考到了。一个扩展欧几里得,解方程的问题,用ax+by=gcd(a,b)解决。当时在想,负数会不会有特殊性?就先按最初讲的函数写了一遍,当b==0,x=1,y=0;再递归带回,却发现答案不对啊,然后陷入沉思。在草稿纸上模拟,又调试了好久,尝试着把b==0时,x=-1,y=1;递归带回,惊喜地发现样例过了。不放心,又自己出了一个数据,7 9,一试,错了。伤心。只好再调试,手动模拟过程,然后发现它的初值x=1,y=0才对。然后就懵了。突然灵光一闪,把最后的aa 分情况,样例中aa是小于0的,而这组数据aa是大于0的,所以用if判断一下。又自己出了几组数据,惊喜的发现全过了。评测也全过了。神奇的方法。

代码如下:

 1 #include<iostream>
 2 #include<cstdio>
 3 using namespace std;
 4 long long a,b;
 5 long long x,y;
 6 void gcd(long long aa,long long bb,long long &x,long long &y)
 7 {
 8     if (bb==0) 
 9     {
10         if (aa>0)
11         {
12             x=1;y=0;return ;
13         } 
14         else
15         {
16             x=-1;y=1;return ;//!!!x=-1,y=1!!!
17         }
18     }
19     else 
20     {
21         gcd(bb,aa%bb,x,y);
22         long long t=x;
23         x=y;
24         y=t-aa/bb*y;
25     }
26 }
27 int main()
28 {
29     freopen("mod.in","r",stdin);
30     freopen("mod.out","w",stdout);
31     cin>>a>>b;
32     gcd(a,-b,x,y);
33     cout<<x;
34     return 0;
35 }
View Code

  其实正解不是我这样的,虽然我还没有仔细的研究这样做的理论证明,而且正解还对x mod b 我表示很惊讶,我没有这么做也是最小值?好好研究一下。

  另附正解(感谢hzwer的代码)

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 #include<cstdlib>
 5 #include<cmath>
 6 #include<algorithm>
 7 #define mod 1000000007
 8 #define ll long long
 9 #define inf (1LL<<60)
10 using namespace std;
11 ll read()
12 {
13     ll x=0,f=1;char ch=getchar();
14     while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
15     while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
16     return x*f;
17 }
18 ll ans;
19 int a,b;
20 void exgcd(int a,int b,int &x,int &y)
21 {
22     if(b==0){x=1;y=0;return;}
23     exgcd(b,a%b,x,y);
24     int t=x;x=y,y=t-a/b*y;
25 }
26 int main()
27 {
28     a=read();b=read();
29     int x,y;
30     exgcd(a,b,x,y);
31     x=(x%b+b)%b;
32     cout<<x<<endl;
33     return 0;
34 }
View Code

  第二题,借教室。第一遍浏览所有题的时候,一看这道题,立马想起前不久学的线段树,然后放心的去编第一题了。可是,现实是残酷的,我连线段树都编错了。还写了三篇博客来着。求心理阴影。更可笑的是,我在每个节点存的居然是和,不应该是最小值吗。Oh,no!好吧,被线段树伤透了心的我只好去网上学习了一个神奇的程序,虽然也用到了正解中的前缀和,但是判断过程和正解不一样,没有用二分,而是依次查找,感觉会很慢,但在cena上全过了,最慢也就0.7s,可是vijos可没给我面子,只得了35'。

代码如下:

 1 #include<iostream>
 2 #include<cstdio>
 3 #define LL long long
 4 using namespace std;
 5 const int maxn=1000005;
 6 int n,m;
 7 LL ri[maxn],p[maxn],sum[maxn];
 8 int pos=123456789,ma;
 9 struct pp{
10     int d,s,t;
11 };
12 pp a[maxn];
13 int main()
14 {
15     freopen("classroom.in","r",stdin);
16     freopen("classroom.out","w",stdout);
17     cin>>n>>m;
18     for (int i=1;i<=n;i++)
19       scanf("%d",&ri[i]);
20     for (int i=1;i<=m;i++)
21     {
22         scanf("%d%d%d",&a[i].d,&a[i].s,&a[i].t);
23         if (a[i].t>ma) ma=a[i].t;
24         p[a[i].s]+=a[i].d;p[a[i].t]+=a[i].d;
25         if ((p[a[i].s]>ri[a[i].s]||p[a[i].t]>ri[a[i].t])&&pos>i)
26           pos=i;
27         sum[a[i].s]+=a[i].d;
28         sum[a[i].t+1]-=a[i].d;
29     }
30     for (int i=1;i<=n;i++)
31       sum[i]+=sum[i-1];
32     for (int i=1;i<=n;i++)
33         if (sum[i]>ri[i])
34         {
35             int tt=0;
36             for (int j=1;j<=m;j++)
37             {
38                 if (j>pos) break;
39                 if (a[j].s<=i&&a[j].t>=i)
40                   tt+=a[j].d;
41                 if (tt>ri[i])
42                 {
43                     pos=j;
44                     break;
45                 }
46             }
47         }
48     if (pos==123456789)
49       printf("0");
50     else 
51     {
52         cout<<-1<<endl;
53         cout<<pos;
54     }
55     return 0;
56 }
View Code

  二分+前缀和、线段树+二分+前缀和 都是对的,这里给出前者的代码:

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<iostream>
 4 #include<string>
 5 #include<iostream>
 6 #include<vector>
 7 #include<cstdlib>
 8 #include<queue>
 9 #include<stack>
10 #include<vector>
11 #include<cmath>
12 #ifdef WIN32
13 #define AUTO "%I64d"
14 #else
15 #define AUTO "%lld"
16 #endif
17 using namespace std;
18 const int maxn=1000000+5;
19 int n,m,r[maxn],d[maxn],s[maxn],t[maxn],a[maxn],sum[maxn];
20 bool flag=true;
21 bool judge(int k)
22 {
23     memset(a,0,sizeof(a));
24     memset(sum,0,sizeof(sum));
25     for (int i=1;i<=k;i++)
26     {
27         a[s[i]]+=d[i]; 
28         a[t[i]+1]-=d[i];
29     }
30     for (int i=1;i<=n;i++)
31     {
32         sum[i]=sum[i-1]+a[i];
33         if (sum[i]>r[i])
34           return false;
35     }
36     return true;
37 }
38 int main()
39 {
40     freopen("classroom.in","r",stdin);
41     freopen("classroom.out","w",stdout);
42     scanf("%d%d",&n,&m);
43     for (int i=1;i<=n;i++) scanf("%d",&r[i]);
44     for (int i=1;i<=m;i++) scanf("%d%d%d",&d[i],&s[i],&t[i]);
45     int L=1,R=m;
46     while (L+1<R)
47     {
48         int mid=(L+R)>>1;
49         if (judge(mid)) L=mid;
50         else R=mid;
51     }
52     bool judge_L=judge(L),judge_R=judge(R);
53     if (!judge_L) printf("-1
%d",L);
54     else if (!judge_R) printf("-1
%d",R);
55     else printf("0");
56     return 0;
57 }
View Code

  第三题,疫情控制。做到这道题的时候,已经没有什么时间了,毕竟刚才的线段树,哎,就不说了。这道题是二分+贪心+倍增(好像不用?),思路嘛,人太懒,就不想打字了,上面都打了这么多了。参考网址:http://blog.csdn.net/u011542204/article/details/47857687  还有http://blog.csdn.net/doyouseeman/article/details/51009073,自我认为解释得还不错。只是今天真的不想去看这种长长的程序了。

  今天还讲了倍增,是同学讲的,讲得不错,帮k啦啦啦。有时间自己再写一篇讲解倍增的博客。发一下同学anantheparty的倍增:http://blog.csdn.net/u011327397/article/details/51894479。

  好啦,今天就先这样吧。(写了这么久的博客。休息一下。)

原文地址:https://www.cnblogs.com/lx0319/p/5697165.html