Thứ Bảy, 23 tháng 3, 2019

Bài E - Codeforces Round #543 (Div.2)

Bài E:
Đề bài: Bạn được cho một đồ thị vô hướng với n đỉnh. Không có chu kỳ cạnh đơn giản với độ dài chẵn trong đó. Hay nói cách khác, không có chu kỳ nào của độ dài chẵn đi qua mỗi cạnh nhiều nhất 1 lần. Chúng ta hãy liệt kê từ 1 đến n.
Bạn phải trả lời q truy vấn. Mỗi truy vấn được mô tả bởi 1 đoạn của đỉnh [l,r] và bạn phải đếm số đoạn con [x;y] của nó (l<=x<=y<=r), thỏa mãn nếu chúng ta xóa tất cả các đỉnh ngoại trừ đoạn [x;y] (bao gồm cả x và y) và cạnh giữa chúng, thì ta được kết quả là đồ thị hai phía.
Đầu vào:
+ Dòng đầu tiên chứa hai số nguyên n và m (1<=n<=3.10^5,1<=m<=3.10^5) - là số đỉnh và số cạnh của đồ thị.
+ m dòng tiếp theo mô tả các cạnh của đồ thị. Dòng thứ i chứa hai số nguyên a(i) và b(i) (1<=a(i),b(i)<=n,a(i)#b(i)), kí hiệu một cạnh giữa hai đỉnh a(i) và b(i). Đảm bảo rằng đồ thị này không chứa khuyên.
+ Dòng tiếp theo số nguyên q (1<=q<=3.10^5)- số truy vấn.
+ q dòng tiếp theo chứa các truy vấn. Dòng thứ i chứa hai số nguyên l(i) và r(i) (1<=l(i)<=r(i)<=n) - tham số truy vấn.
Đầu ra:
In ra q số, mỗi số một dòng. Dòng thứ i là số đoạn con [x;y], thỏa mãn rằng chỉ chứa các đỉnh từ đoạn [x;y] và các cạnh giữa chúng là lưỡng phân.
Ví dụ:
Đầu vào:
6 6
1 2
2 3
3 1
4 5
5 6
6 4
3
1 3
4 6
1 6
Đầu ra:
5
5
14
/***/
Đầu vào:
8 9
1 2
2 3
3 1
4 5
5 6
6 7
7 8
8 4
7 2
3
1 8
1 4
3 8
Đầu ra:
27
8
19.
Chú ý:
Ví dụ đầu được biểu diễn như sau:

Với truy vấn đầu tiên, tất cả các đoạn con của [1;3], ngoại trừ chính nó, là thích hợp.
Với truy vấn thứ hai, tất cả các đoạn con của [4;6], ngoại trừ  chính nó, là thích hợp.
Với truy vấn thứ ba, tất cả các đoạn con của [1;6] là thích hợp, ngoại trừ [1;3],[1;4],[1;5],[1;6],[2;6],[3;6],[4;6].
Ví dụ thứ hai được biểu diễn như hình dưới đây:

Hướng dẫn: Nếu hai chu kỳ có độ dài lẻ giao nhau, thì chúng có thể bỏ qua để mà đạt được một chu trình có độ dài chẵn của một cạnh đơn giản.
Điều này dẫn tới đồ thị được cho là môt đỉnh cactus (cây xương rồng), với chu trình có độ dài lẻ, vì vậy đoạn đỉnh là tốt - nếu nó không có khuyên, nghĩa là đỉnh với số nhỏ nhất từ chu trình này được hiển thị trên đoạn này và đỉnh với số lớn nhất từ chu trình này hiển thị trên đoạn này. Vì vậy chúng ta có thể chọn tất cả các chu trình, và bây giờ chúng ta làm việc với đoạn.
Chúng ta hãy tìm mỗi đỉnh với biên phải nhỏ nhất trên đoạn, thỏa mãn khoảng [i...mx(i)] là một đồ thị lưỡng phân.
Để trả lời các truy vấn, chúng ta cần phải tổng hợp mx(i)-i+1 cho chúng mà có mx(i)>=r và tổng của r-i+1 cho những cái mà có mx(i)>=r.
Vì vậy, chúng tôi kí hiệu rằng mx(i) tăng và chúng tôi suy ra cần phải tìm thời điểm đầu tiên mà mx(i) trở nên >=r (chúng ta có thể làm việc này với tìm kiếm nhị phân)
Và sau đó tính hai tổng trước - tổng của các (mx(i)-i+1) và tổng của các i.
Lời giải:
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+7;
vector<int>G[N];
int vis[N]={0},p[N];
long long sum[N]={0};
stack<int>s;
void dfs(int u,int fa){
 s.push(u); 
 vis[u]=1;
 for(int i=0;i<(int)G[u].size();i++){
  int v=G[u][i];
  if(v==fa) continue;
  if(!vis[v]) dfs(v,u);
  else if(vis[v]==1){
   int maxx=u,minn=u;
   while(!s.empty()){
    int t=s.top();s.pop();
    maxx=max(t,maxx);minn=min(t,minn);
    if(t==v) break;
   }
   p[minn]=maxx;
  }
 }
 if(!s.empty()&&s.top()==u) s.pop();
 vis[u]=2;
} 
int main(){
 ios::sync_with_stdio(false);cin.tie(0);
 int n,m;cin>>n>>m;
 for(int i=0;i<m;i++){
  int x,y;cin>>x>>y;
  G[x].push_back(y); G[y].push_back(x);
 }
 for(int i=1;i<=n+1;i++) p[i]=n+1;
 for(int i=1;i<=n;i++) if(!vis[i]) dfs(i,-1);
 for(int i=n;i>=1;i--){
  p[i]=min(p[i],p[i+1]);
  sum[i]=sum[i+1]+p[i]-i;
 } 
 int q;cin>>q;
 while(q--){
  int l,r;cin>>l>>r;
  int L=l,R=r+1;
  while(L<R){
   int m=(L+R)/2;
   if(p[m]>r) R=m;
   else L=m+1;
  }
  cout<<sum[l]-sum[L]+1ll*(r-L+1)*(r-L+2)/2<<endl;
 }
 return 0;
}

Thứ Sáu, 22 tháng 3, 2019

Bài D - Codeforces Round #543 (Div.2)

Bài D:
Đề bài: Giả sử bạn có hai đa thức A(x)=a(0)x^0+a(1)x^1+...+a(n)x^n và B(x)=b(0)x^0+b(1)x^1+...+b(m)x^m. Đa thức A(x) có thể biểu diễn duy nhất dưới dạng sau A(x)=B(x)D(x)+R(x), deg R(x)<deg B(x).
Điều này có thể được thực hiện bằng cách sử dụng long division. Ở đây, deg P(x) kí hiệu là bậc của đa thức P(x). R(x) được gọi là dư của phép chia của đa thức A(x) cho B(x), và nó kí hiệu là A mod B.
 Bởi vì đây là một cách để chia đa thức với số dư, chúng ta có thể sử dụng thuật toán Euclide để tìm ước chung lớn nhất của hai đa thức. Thuật toán lấy hai đa thức (A,B). Nếu đa thức B(x) bằng 0, thì kết quả là A(x) và ngược lại kết quả của giá trị của thuật toán sẽ trả về cặp (B, A mod B). Cứ mỗi bước, bậc của đa thức 2 sẽ giảm xuống, vì vậy thuật toán làm việc hữu hạn lần. Nhưng số lượng đó lớn bao nhiêu? Bạn phải trả lời cho câu hỏi này.
 Bạn được cho một số nguyên n. Bạn phải xây dựng hai đa thức với bậc không vượt quá n, thỏa mãn rằng hệ số của chúng đều là số nguyên không vượt quá 1 bởi giá trị tuyệt đối của chúng, hệ số dẫn đầu (hệ số đi với số mũ lớn nhất của x) đều bằng 1, thuật toán Euclid được mô tả biểu diễn chính xác n bước tìm ước chung lớn nhất. Hơn thế nữa, bậc của đa thức đầu tiên phải lớn hơn bậc của đa thức thứ hai. Từng bước của thuật toán có nghĩa là từ cặp (A,B) đến cặp (B, A mod B)
Đầu vào:
 Bạn được cho số nguyên n (1<=n<=150) - số bước của thuật toán bạn cần đạt tới.
Đầu ra:
Hai đa thức dưới dạng sau:
+ Dòng đầu tiên chứa số nguyên m (0<=m<=n)- là bậc của đa thức.
+ Dòng thứ hai chứ m+1 số nguyên từ -1 đến 1 - là hệ số của đa thức, từ hằng số đầu tiên dẫn đầu.
+ Bậc của đa thức đầu tiên lớn hơn hoặc bằng bậc của đa thức thứ hai, hệ số dẫn đầu bằng 1. Thuật toán Euclide nên biểu diễn chính xác n bước khi gọi sử dụng đa thức này.
Nếu không có câu trả lời cho n, in ra -1
Nếu có nhiều đáp án, in ra đáp án bất kì.
Ví dụ:
Đầu vào:
1
Đầu ra:
1
0 1
0
1
/***/
Đầu vào:
2
Đầu ra:
2
-1 0 1
1
0  1
Chú ý:
Ở ví dụ thứ 2, bạn có thể in đa thức x^2-1 và x. Chuỗi này như sau: (x^2-1,x)->(x,-1)->(-1,0).
Có hai bước trong nó.
Hướng dẫn: Đối với số nguyên, chúng ta biết rằng trường hợp xấu nhất là dãy Fibonacci F(n+1)=F(n)+F(n-1). Giải pháp của vấn đề này là dựa trên ý tưởng trên. Có hai giải pháp được dự định. Đầu tiên bạn nên chú ý rằng: p(0)=1,p(1)=x, p(n+1)=x.p(n)+(-) p(n-1) cho ta một họ các giải pháp, chúng ta phải output p(n) và p(n-1). Nó có thể kiểm tra trực tiếp cho những hạn chế được cho mà bạn luôn chọn + hoặc - để thõa mãn hạn chế hằng số.
 Một giải pháp thứ hai là cùng với dãy trên nhưng bạn sử dụng dấu + thay vì +(-) và lấy hệ số module 2. Điều này cũng đúng vì nếu dãy những số dư có k bước trong khi bạn xét các số bởi cùng modulo nó sẽ có ít nhất k bước trong các số hợp lý. Vì vậy giải pháp thứ hai là: p(0)=1,p(1)=x,p(n+1)=x.p(n)+p(n-1) mod 2.
Lời giải:
#include<bits/stdc++.h>
using namespace std;
bitset<1000>a[1005];
int main()
{
    int n,i,j;
    cin>>n;
    a[1]=1;
    for(i=2;i<=n+1;i++)a[i]=(a[i-1]<<1)^a[i-2];
    cout<<n<<endl;
    for(i=0;i<=n;i++)cout<<a[n+1][i]<<' ';cout<<endl;
    cout<<n-1<<endl;
    for(i=0;i<=n-1;i++)cout<<a[n][i]<<' ';

    return 0;
}

Bài C - Codeforces Round #543 (Div. 2)

Bài C:
Đề bài: Sasha đang tham gia một cuộc thi lập trình. Trong một các vấn đề, cô ấy phải kiểm tra liệu rằng một vài rễ cây có phải isomorphic hay không. Cô ấy chưa bao giờ nhìn thấy vấn đề này trước đó, nhưng, là một thí sinh có kinh nghiệm, cô ấy đoán rằng cố ấy nên nối các cây với một vài dãy và sau đó so sánh các dãy này thay vì các cây. Sasha muốn nỗi mỗi cây với một dãy a(0),a(1),...,a(h) trong đó h là chiều cao của cây, và a(i) bằng số các đỉnh mà nằm tại khoảng cách của cạnh i từ rễ.
 Không may mắn thay, vào lúc này trực giác (intuition) đã bị sai, và có thể có một vài cây nối cùng một dãy. Để chứng minh điều này, bạn cần viết một chương trình , cho một chuỗi a(i), xây dựng hai cây có rễ không hẻo lánh mà nối các dãy này, hoặc xác định rằng chỉ có 1 cái cây như vậy.
  Hai cây có rễ được gọi là hẻo lánh, nếu bạn có thể tái hiện (reenumerate) các đỉnh của cây đầu tiên theo cách này, có nghĩa là chỉ số của rễ trở nên bằng chỉ số của rễ cây thứ hai, và hai cây này trở nên bằng nhau.
 Chiều cao của một cây có rễ là số cạnh lớn nhất trên đường đi từ rễ đến các đỉnh khác bất kỳ.
Đầu vào:
+ Dòng đầu tiên chứa một số nguyên h(2<=h<=10^5) - là chiều cao của cây.
+ Dòng thứ hai chứ h+1 số nguyên - một dãy a(0),a(1),...,a(h)(1<=a(i)<=2.10^5). Tổng của tất cả các a(i) không vượt quá 2.10^5. Nó đảm bảo rằng có ít nhất một cây nối dãy này.
Đầu ra:
+ Nếu chỉ có một cây nối dãy này, in ra 'perfect'
+Ngược lại, in 'ambiguous' ở dòng đầu tiên.
Dòng thứ hai và dòng thứ ba in ra mô tả hai cây theo dạng sau:
- Một dòng in (a(0)+a(1)+...+a(h)), phần tử thứ k của chúng có thể là bố mẹ đỉnh k hoặc cùng bằng 0, nếu đỉnh k là rễ.
Những cái cây này không hẻo lánh và nối với dãy đã cho.
Ví dụ:
Đầu vào:
2
1 1 1
Đầu ra:
perfect
/***/
Đầu vào:
2
1 2 2
Đầu ra:
ambiguous
0 1 1 3 2
0 1 1 3 2
Chú ý:
Chỉ có một cây ở ví dụ đầu tiên và hai cây được in từ ví dụ 2 được biểu diễn như các hình dưới đây.

Hướng dẫn: Có rất nhiều cách để giải bài toán này. Việc đầu tiên chúng ta xây dựng một cây đơn bất kì. Để làm việc này, chúng ta đầu tiên xây dựng đường đi dài nhất từ rễ và sau đó đính kèm đính kèm vào những đỉnh duy trì một chiều cao thích hợp. Vì vậy mỗi đỉnh hoặc là đường đi dài nhất hoặc có bố mẹ trên đường này. Để xây dựng cây thứ hai bạn nên sử dụng những đỉnh khác nhau từ bậc trước trong suốt việc xây dựng để tạo ra nó khác với cây đầu tiên. Điều này luôn luôn có thể nếu có hai a(i) liên tiếp >1. Mặt khác cây được xác định độc nhất bởi dãy a(i).
Lời giải:
#include<bits/stdc++.h>
using namespace std;
int n,a[200005],m[200005],i=0;
int main(){
 cin>>n;
 while(i<=n) cin>>a[i++];
 for(i=0;i<n;i++) if(a[i]>1 && a[i+1]>1){
  cout<<"ambiguous\n";
  int l=0,k=0,t;
  for(int j=0; j<=n; j++){
   while(a[j]--) m[k++]=l;
   l=k;
   if(j==i) t=k;
  }
  l=2;
  while(l--){
   for(int j=0; j<k; j++) cout<<((j)? " ":"")<<((j==t && l)? m[j]-1:m[j]);
   cout<<"\n"; 
  }
  return 0;
 }
 cout<<"perfect\n";
}

Bài B - Codeforces Round #543 (.Div 2)

Bài B:
Đề bài: Bạn được cho một cái cây có rễ với n đỉnh. Các đỉnh được đánh số từ 1 đến n, rễ cây được đánh số 1.
 Mỗi đỉnh có một màu, chúng ta ký hiệu màu của đỉnh v là c(v). Ban đầu c(v)=0.
  Bạn phải tô màu cho cây thành các màu nhất định sử dụng ít bước nhất có thể. Mỗi bước bạn có thể chọn một đỉnh v và một màu x, và sau đó tô màu tất cả các đỉnh của cây con đỉnh v (bao gồm cả đỉnh v) bằng màu x. Hay nói cách khác, với mỗi đỉnh u, thỏa mãn đường đi từ gốc đến u xuyên qua các đỉnh v, ta đặt c(u)=x.
Đảm bảo rằng bạn phải tô màu mỗi đỉnh bằng một màu khác 0.
Đầu vào:
+ Dòng đầu tiên cho số nguyên n (2<=n<=10^4) - số đỉnh của một cây.
+ Dòng thứ hai chứa n-1 số nguyên p(2),p(3),...,p(n)(1<=p(i)<i), ở đây p(i) có nghĩa là có một cạnh giữa đỉnh đỉnh i và p(i).
+ Dòng thứ ba chứa n số nguyên c(1),c(2),...,c(n) (1<=c(i)<=n), ở đây c(i) là màu bạn nên tô cho đỉnh thứ i.
Đảm bảo rằng đồ thị đã cho là một cây.
Đầu ra:
In một số nguyên- số bước tối thiểu bạn phải thực hiện tô màu bằng các màu nhất định.
Ví dụ:
Đầu vào:
6
1 2 2 1 5
2 1 1 1 1 1
Đầu ra:
3
/********/
Đầu vào:
7
1 1 2 3 1 4
3 3 1 1 1 1 2 3
Đầu ra:
5
/********/
Chú ý:
Cây từ ví dụ đầu tiên được biểu diễn dưới hình dưới đây (các số là chỉ số của các đỉnh)

Ở bước đầu tiên ta tô tất cả các đỉnh của cây con của đỉnh 1 bằng màu 2.

Bước 2 ta tô màu tất cả các đỉnh của cây con đỉnh 5 bằng màu 1

Ở bước thứ 3, ta tô màu tất cả các đỉnh cây con đỉnh 2 bằng màu 1

/***/
Cây từ ví dụ 2 được biểu diễn như sau:

Ở bước đầu tiên chúng ta tô tất cả các đỉnh của cây con đỉnh 1 bằng màu 3

Ở bước thứ 2 ta tô tất cả các đỉnh của cây con đỉnh 3 bằng màu 1

Ở bước thứ 3 ta tô tất cả các đỉnh của cây con đỉnh 6 bằng màu 2

Ở bước thứ 4 chúng ta tô màu tất cả các đỉnh của cây con đỉnh 4 bằng màu 1

Ở bước thứ 5 ta tô tất cả các đỉnh của cây con đỉnh 7 bằng màu 3

Hướng dẫn: Ta xét quá trình from the end, chúng ta sẽ xóa một cây con bất kì từ cây, màu tổ tiên đỉnh cao nhất của cây con đó phải khác với màu của đỉnh cao nhất và màu của tất cả các đỉnh của cây con phải giống nhau. Vì vậy, chúng ta thể có thể chứng minh được rằng đáp án sẽ là số cạnh mà kết thúc của chúng có số màu khác nhau +1.
Lời giải:
#include<cstdio>
int n,a[10010],f[10100],s,i;
main(){
    scanf("%d",&n);
    for(i=2;i<=n;++i)scanf("%d",f+i);
    for(i=1;i<=n;++i)scanf("%d",a+i),a[i]!=a[f[i]]?++s:0;
    printf("%d",s);
}

Bài A- Codeforces Round #453 (.Div 2)

Bài A:
Đề bài: Pig đang thăm 1 người bạn.
Nhà của Pig tọa lạc tại vị trí 0 và nhà của bạn Pig tọa lạc tại vị trí m trên trục tọa độ.
Pig có thể sử dụng teleport (một phương tiện di chuyển) để đi học trục tọa độ.
Để sử dụng teleport, Pig nên đi đến một điểm nhất định (cái điểm mà teleport đang tọa lạc) và chọn nơi đó để di chuyển ; mỗi lần teleport, có một điểm rightmost để nó có thể đưa Pig đến đó, điểm này được biết như là điểm giới hạn của teleport.
  Chính thức là, một teleport tọa lạc tại vị trí x với giới hạn y có thể chuyển Pig từ điểm x đến bất kỳ điểm nào trong đoạn [x;y]; bao gồm cả biên.
Xác định, liệu rằng Pig có thể thăm nhà bạn của anh ấy mà chỉ sử dụng teleport hay không, hay anh ấy nên sử dụng xe hơi của mình.
Đầu vào: 
+ Dòng đầu tiên chứa hai số nguyên n và m (1<=n<=100,1<=m<=100) , ở đây n và m lần lượt là số teleport và địa điểm nhà của bạn của Pig.
+ n dòng tiếp theo chứa thông tin về các teleport
+ Dòng thứ i chứa hai số nguyên a(i) và b(i) (0<=a(i)<=b(i)<=m), ở đây a(i) là vị trí của teleport thứ i và b(i) là giới hạn của nó.
Đảm bảo rằng a(i)>=a(i-1) với mỗi i (2<=i<=n).
Đầu ra:
+ In ra YES nếu có một đường đi từ nhà Pig đến nhà của bạn Pig mà chỉ sử dụng các teleport, và in ra NO trong trường hợp ngược lại.
Bạn có thể in ra các ký tự YES hay NO tùy ý. (tức là không phân biệt hoa hay thường).
Ví dụ 1:
Đầu vào:
3 5
0 2
2 4
3 5
Đầu ra:
YES
Ví dụ 2:
Đầu vào:
3 7
0 4
2 5
6 7
Đầu ra:
NO
Chú ý:
+Ví dụ đầu tiên được biểu diễn như hình vẽ dưới đây:
Pig có thể sử dụng teleport thứ nhất từ nhà cậu ấy (điểm 0) đến điểm 2, sau đó sử dụng teleport thứ hai đi từ điểm 2 đến điểm 3, và cuối cùng sử dụng teleport thứ 3 đi từ điểm 3 đến điểm 5, nơi mà anh bạn của Pig đang sống.
+ Ví dụ thứ 2 được biểu diễn như hình vẽ dưới đây:
Bạn có thể nhìn thấy rằng không có đường nào từ nhà của Pig đến nhà bạn của Pig mà chỉ sử dụng các teleports.
Hướng dẫn: Chú ý rằng nếu chúng ta có thể đến được một điểm x nào đó, thì tất cả những điểm chúng ta đến được phải <=x. Vì vậy, chúng ta có thể giả sử điểm rightmost, nơi mà chúng ta đến. Thế thì nếu điểm này có thể sử dụng teleport (nếu điểm này ở bên phải của teleport), chúng ta sẽ thử di chuyển nó (nếu giới hạn của teleport là điểm right của điểm hiện tại, thì di chuyển nó đến đó). Và cuối cùng chúng ta kiểm tra rằng điểm rightmost nơi mà chúng ta đến có bằng m hay không?
Lời giải:
#include <stdio.h>
int n,m,c,l,r;
int main(){
    scanf("%d%d",&n,&m);
    while(n--)scanf("%d%d",&l,&r),c=(l<=c&&r>c)?r:c;
    printf(c>=m?"YES":"NO");
    return 0;
}

Thứ Năm, 21 tháng 3, 2019

Bài G - Codeforces Round #547 (Div.3)

Bài G:
Đề bài: Một vùng đất bao gồm n thành phố và n-1 đường đi. Mỗi đường là đường hai chiều và kết nối hai thành phố phân biệt. Từ bất kì thành phố nào bạn cũng có thể đến bất kì thành phố khác bằng các con đường. Vâng, bạn đã đúng - cấu trúc của miền quê là một cây vô hướng.
 Có một vài công ty tư về đường xá và giao thông trong mãnh đất này. Chính phụ quyết định bán các con đường này cho công ty. Mỗi đường sẽ thuộc về một công ty và một công ty có thể quản lý nhiều đường.
  Chính phủ sợ không công bằng. Họ nghĩ rằng con người trong thành phố này có thể xem họ không công bằng nếu có một công ty mà nó sở hữu hai hoặc nhiều đường vào thành phố. Chính phủ muốn tư nhân hóa rằng số các thành phố như vậy không được quá k và số công ty tham gia vào việc tư nhân hóa này là tối thiểu.
 Chọn số lượng công ty r sao cho có thể chỉ định mỗi con đường cho một công ty theo cách sao cho số lượng thành phố có hai hoặc nhiều con đường của một công ty nhiều nhất là k. Hay nói cách khác, nếu cho một thành phố tất cả các đường thuộc về các công ty khác nhau thì thành phố đó good. Nhiệm vụ của bạn là xác định số r nhỏ nhất để thỏa mãn rằng việc chỉ định các công ty từ 1 đến r thỏa mãn số thành phố không tốt không vượt quá k.

Bức tranh này mô tả ví dụ đầu tiên (n=6,k=2). Câu trả lời là 2 công ty. Các số trên các cạnh kí hiệu chỉ số của cạnh. Các cạnh được tô màu có nghĩa là các công ty: màu đỏ tương ứng với công ty thứ nhất, màu xanh tương ứng với công ty thứ 2. Đỉnh màu xám (số 3) là không tốt. Số đỉnh như vậy (chỉ có 1) không vượt quá k=2. Nó không thể có tối đa k=2 thành phố không tốt trong trường hợp 1 công ty.
Đầu vào: 
+ Dòng đầu tiên chứa hai số nguyên n và k (2<=n<=200000,0<=k<=n-1)- số các thành phố và số thành phố tối đa mà có thể có hai hoặc nhiều đường hơn thuộc về công ty nó.
+ n-1 dòng tiếp theo chứa các đường, mỗi đường một dòng.
Đầu ra: 
+ Dòng đầu tiên chứa số r cần tìm (1<=r<=n-1).
+ Dòng thứ hai in n-1 số c(1),c(2),...,c(n-1)(1<=c(i)<=r), trong đó c(i) là công ty sở hữu đường thứ i. Nếu có nhiều đáp án, in ra đáp án bất kì.
Ví dụ:
Đầu vào:
6 2
1 4
4 3
3 5
3 6
5 2
Đầu ra: 
2
1 2 1 1 2.
Hướng dẫn: Chính thức là, vấn đề ở đây là tô màu các cạnh của cây với số màu tối thiểu theo một cách nào đó sao cho số đỉnh không đúng không vượt quá k. Một đỉnh là không đúng nếu có út nhất hai cạnh trực thuộc cùng màu.
 Dễ dàng chỉ ra rằng D màu luôn luôn đủ để tô cây theo một cách nào đó thỏa mãn tất cả các đỉnh đều đúng, ở đây D là giá trị lớn nhất trong các bậc của đỉnh. Thật vậy, điều này luôn đúng với mọi đồ thị hai phía.
 Trong bài toán này, bạn có thể có tới k đỉnh không đúng, vì vậy chỉ cần chọn d nhỏ nhất sao cho số đỉnh có bậc lớn hơn hoặc bằng d nhiều nhất là k. Trong giải pháp khác là bạn có thể sử dụng tìm kiếm nhị phân để tìm số d như vậy, nhưng nó khó thực hiện hơn và giải pháp này trở nên chậm hơn do nhân tố log.
 Sau khi nó tô các cạnh cạnh với d màu, mỗi lần chọn một màu tiếp theo (bỏ qua màu nếu nó bằng với số màu của cạnh đang duyệt tới).
Lời giải:
#include<bits/stdc++.h>
using namespace std;
int n, k, r;
vector<vector<pair<int,int>>> g;
int D;
vector<int> col;

void dfs(int u, int p, int f) {
    int color = 0;
    for (auto e: g[u])
        if (p != e.first) {
            if (color == f) {
                color = (color + 1) % D;
                f = -1;
            }
            col[e.second] = color;
            dfs(e.first, u, color);
            color = (color + 1) % D;
        }         
}

int main() {
    cin >> n >> k;
    g.resize(n);
    vector<int> d(n);
    for (int i = 0; i + 1 < n; i++) {
        int x, y;
        cin >> x >> y;
        x--, y--;
        g[x].push_back({y, i});
        g[y].push_back({x, i});
        d[x]++, d[y]++;
    }
    map<int,int> cnt;
    for (int dd: d)
        cnt[dd]++;
    
    int kk = n;
    D = 0;
    for (auto p: cnt)
        if (kk > k)
            D = p.first,
            kk -= p.second;
        else
            break;
    col = vector<int>(n - 1);
    dfs(0, -1, -1);    
    cout << D << endl;
    for (int i = 0; i + 1 < n; i++)
        cout << col[i] + 1 << " ";
}
 

Thứ Tư, 20 tháng 3, 2019

Bài F1+F2 - Codeforces Round #547 -(Div.3)

Bài F1+F2:
Đề bài: Vấn đề này được cho gồm hai phiên bản, và chúng chỉ khác nhau về ràng buộc của n.
Bạn được cho một mảng các số nguyên a(1),a(2),...,a(n). Một block là một dãy các phần tử liên tiếp a(l),a(l+1),...,a(r)(1<=l<=r<=n). Vì vậy, một block được định nghĩa bởi một cặp chỉ số (l,r).
 Tìm một tập các block (l(1),r(1)),(l(2),r(2)),...,(l(k),r(k)) thỏa mãn:
+ Chúng không được giao nhau (nghĩa là chúng rời nhau). Chính thức là, với mỗi cặp block (l(i),r(i)) với i#j thì hoặc r(i)<l(j) hoặc r(j)<l(i).
+ Với mỗi block, tổng các phần tử của chúng phải bằng nhau. Chính thức là, a(l(1))+a(l(1)+1)+...+a(r(1))=a(l(2))+a(l(2)+1)+...+a(r(2))=...=a(l(k))+a(l(k)+1)+...+a(r(k)).
+ Số block trong một tập là lớn nhất. Chính thức là, không tồn tại một tập các block (l(1)',r(1)'),(l(2)',r(2)'),...,(l(k)',r(k)') thỏa mãn cả hai yêu cầu trên với k'>k.
Viết chương trình tìm tập như vậy.
Đầu vào:
+ Dòng đầu tiên chứa số nguyên n (1<=n<=50)- Độ dài của mảng được cho.
+ Dòng thứ hai chứa một dãy các số nguyên a(1),a(2),...,a(n)(-10^5<=a(i)<=10^5).
Đầu ra:
+ Dòng đầu tiên in số nguyên k(1<=k<=n).
+ k dòng tiếp theo chứa các block, mỗi cái một dòng. Với mỗi dòng in một cặp chỉ số l(i),r(i)(1<=l(i)<=r(i)<=n)- là giới hạn của block thứ i.
+ Bạn có thể in block theo bất kỳ thứ tự nào. Nếu có nhiều đáp án, in ra bất kì.
Ví dụ:
Đầu vào:
7
4 1 2 2 1 5 3
Đầu ra:
3
7 7
2 3
4 5
Hướng dẫn: Gọi x là tổng giống nhau của các block trong câu trả lời. Quan sát thấy rằng, x có thể biểu diễn dưới dạng tổng của các phần tử kề nhau trong a, có nghĩa là x=a(l)+a(l+1)+...+a(r) với l và r nào đó.
Duyệt qua tất cả các trường hợp có thể của block với độ phức tạp O(n^2) và lưu lại tất cả các block. Bạn có thể sử dụng công cụ 'map<int, vector<pair<int,int>>>' để lưu block được tạo bởi cùng một tổng. Bạn có thể làm nó theo đoạn code sau:
 
map<int,vector<pair<int,int>>> segs;
for (int r = 0 ; r < n ; r++){
    int sum = 0;
   for ( int l = r ; l >= 0; l-- ){
      sum+=a[l];
     segs[sum].push_back({l,r});
}
}

Chú ý rằng, các block này được sắp xếp theo đầu bên phải trong mỗi nhóm.
Sau đó, bạn có thể độc lập thử mỗi nhóm (có O(n^2) chúng) và tìm kiếm tập rời rạc gồm các block cùng một nhóm có số lương lớn nhất. Bạn có thể làm việc này bằng phương pháp tham lam, mỗi lần đưa vào phân đoạn (segment) trả lời với đầu bên phải nhỏ nhất. Bởi vì mỗi nhóm chúng đều được sắp theo thứ tự đầu bên phải nên bạn có thể tìm thấy khối tách rời tối đa cần thiết với một lần duyệt. Chúng ta giả sử pp là nhóm gồm các block hiện tại (chúng được sắp xếp theo the right end), thì đoạn code dưới đây xây dựng tập rời rạc lớn nhất:

int cur = 0;
int r = -1;
vector<pair<int,int>> now;
for(auto seg : pp)
   if ( seg.first > r){
     cur++;
     now.push_back(seg);
    r=seg.second;
}
Chọn giá trị lớn nhất trong số các tập phân biệt lớn nhất cho nhóm.
Lời giải:
#include<bits/stdc++.h>
using namespace std;
int main(){
    int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++)
    cin >> a[i];
map<int, vector<pair<int,int>>> segs;
for (int r = 0; r < n; r++) {
    int sum = 0;                              
    for (int l = r; l >= 0; l--) {
        sum += a[l];
        segs[sum].push_back({l, r});
    }
}
int result = 0;
vector<pair<int,int>> best;
for (const auto& p: segs) {
    const vector<pair<int,int>>& pp = p.second;
    int cur = 0;
    int r = -1;
    vector<pair<int,int>> now;
    for (auto seg: pp)
        if (seg.first > r) {
            cur++;
            now.push_back(seg);
            r = seg.second;
        }
    if (cur > result) {
        result = cur;
        best = now;
    }
}
cout << result << endl;
for (auto seg: best)
    cout << seg.first + 1 << " " << seg.second + 1 << endl;
    return 0;
}

Bài G - Educatioal Round 62

Đề bài: Bạn được cho 1 đồ thị vô hướng đặc biệt. Nó bao gồm $2n$ đỉnh được đánh số từ 1 đến 2n. Dưới đây là một số đặc tính của đồ thị: + ...