2018, XI Samara Regional Intercollegiate Programming Contest
A. Restoring Numbers
題意:
給出兩個數 和 的和 和最大公因數 ,要你求出任意一組 和 的解。
題解:
我們可以知道 ,則我們假設 ,,那麼 ,由此可得有解的第一個條件是 。
至於爲什麼是第一個,是因爲題目描述 和 是正數,所以上設的 和 要大於 ,所以有解的第二個條件是 ,此時我們令 ,,就得到了 和 的一組解,同時就得到了 和 的一組解。
代碼:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int main(){
int s,g;scanf("%d%d",&s,&g);
if(s%g!=0||s/g==1) printf("-1\n");
else printf("%d %d\n",g,s-g);
return 0;
}
B. Minimal Area
題意:
按順時針方向給出一個凸包上的所有點,問你以這個凸包上的點爲頂點的非退化三角形的最小面積(的兩倍,這個兩倍應該是爲了方便計算)。
題解:
考慮凸包的任意一條邊作爲底邊,底邊相同時高最小面積最小,而能做到高最小的頂點即爲離底邊最近的點,即從凸包上取相鄰三點一定能找到最小的三角形,所以我們每次順時針取三個點計算面積取最小值。
代碼:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=200005;
struct vec{
ll x,y;
}p[maxn];
vec create(vec a,vec b){
return vec{b.x-a.x,b.y-a.y};
}
ll area(vec a,vec b){
ll res=(a.x*b.y)-(b.x*a.y);
return res<0?-res:res;
}
int main(){
int n;scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld%lld",&p[i].x,&p[i].y);
p[n+1]=p[1];p[n+2]=p[2];
ll ans=0x7fffffffffffffff;
for(int i=1;i<=n;i++){
vec a=create(p[i],p[i+1]);
vec b=create(p[i],p[i+2]);
ans=min(ans,area(a,b));
}
printf("%lld\n",ans);
return 0;
}
C. Third-Party Software
題意:
給出 條線段,問你最少選擇多少個點,使得每條線段至少包含一個點,並給出這些點。
題解:
對線段進行排序,記錄當前存放的節點,初始爲 。當遍歷到的線段不包含當前點時,把這條線段的右端點放進答案,並更新當前點。這樣首先能保證每條線段至少覆蓋一個點,其次由於每次貪心選擇右端點,保證選擇的點儘可能少。
代碼:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=200005;
struct node{
int l,r;
bool operator<(const node &t)const{
if(r!=t.r) return r<t.r;
else return l<t.l;
}
}p[maxn];
int ans[maxn],tot;
int main(){
int n;scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d%d",&p[i].l,&p[i].r);
sort(p,p+n);
int now=p[0].r;
ans[tot++]=now;
for(int i=1;i<n;i++){
if(p[i].l>now){
now=p[i].r;
ans[tot++]=now;
}
}
printf("%d\n",tot);
for(int i=0;i<tot;i++) printf("%d%c",ans[i],(i==tot-1)?'\n':' ');
return 0;
}
D. Transfer Window
題意:
總共有 類球員,你手裏有 個種類爲 ,,…, 的球員,你想通過和另一個隊伍進行一系列交換最終獲得 個種類爲 ,,…, 的球員。有一個 的矩陣,每個有序對 的值爲 或 ,若爲 則表示你可以通過手中有的 交換得到 。一次交換後你失去 並獲得 ,另一個隊伍失去 並獲得 。問能否找到一個交換方式滿足條件,如果有還要輸出交換次數和交換內容,不需要控制交換次數最少。
思路:
, 只有 的數據範圍,可以想到一個網絡流的做法來求是否有解,但是沒有考慮好如何輸出交換方案。
題解:
未通過
E. Substring Reverse
題意:
給出兩個串 和 ,問是否可以通過恰好一次如下操作使 與 相同:
把 的某個子串反轉。
題解:
首先子串當然可以是單個字符。
然後由於恰好一次,所以我們只需要找到兩個串第一個字符不相同的位置和最後一個字符不相同的位置,判斷把中間整個部分反轉能否實現即可。
代碼:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=200005;
char s[maxn],t[maxn];
int main(){
scanf("%s%s",s+1,t+1);
int len=(int)strlen(s+1);
bool flag=true;
int st=maxn,ed=-1;
for(int i=1;i<=len;i++){
if(s[i]!=t[i]){
st=min(st,i);
ed=max(ed,i);
}
}
if(ed!=-1){
for(int i=st,j=ed;i<=ed;i++,j--){
if(s[i]!=t[j]){
flag=false;
break;
}
}
}
if(flag) printf("YES\n");
else printf("NO\n");
return 0;
}
F. Tree Restoration
題意:
告訴你每個點的所有後代,問你是否存在這樣一棵樹,如果存在輸出每條樹邊。
題解:
正向存邊,進行拓撲排序,每次檢查被遍歷到的點和當前點的父節點的祖先是否相同,並每次更新父節點。最後檢查找到的樹是否滿足初始的邊的關係。
代碼:
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
typedef pair<int,int> pii;
const int maxn=1005;
int ind[maxn],fa[maxn];
int mp[maxn][maxn];
vector<int> vec[maxn];
int main(){
int n;scanf("%d",&n);
for(int i=1;i<=n;i++) fa[i]=-1;
for(int i=1;i<=n;i++){
int c;scanf("%d",&c);
for(int j=0;j<c;j++){
int x;scanf("%d",&x);
vec[i].push_back(x);
mp[i][x]=1;
ind[x]++;
}
}
queue<int> q;
int rt=-1;
for(int i=1;i<=n;i++){
if(!ind[i]){
q.push(i);
fa[i]=i;
rt=i;
}
}
if((int)q.size()!=1){
printf("NO\n");
return 0;
}
int cnt=1;
while(!q.empty()){
int now=q.front();q.pop();
int sz=(int)vec[now].size();
for(int i=0;i<sz;i++){
int to=vec[now][i];
if(fa[to]!=fa[now]&&fa[to]!=-1){
printf("NO\n");
return 0;
}
fa[to]=now;
ind[to]--;
if(!ind[to]){
q.push(to);
cnt++;
}
}
}
if(cnt!=n){
printf("NO\n");
return 0;
}
for(int i=1;i<=n;i++){
if(i==rt) continue;
for(int j=0,sz=(int)vec[i].size();j<sz;j++){
int to=vec[i][j];
if(!mp[fa[i]][to]){
printf("NO\n");
return 0;
}
}
}
printf("YES\n");
for(int i=1;i<=n;i++){
if(i!=rt) printf("%d %d\n",fa[i],i);
}
return 0;
}
G. Underpalindromity
題意:
給出 個數,對於每個長度爲 的子區間,你可以花費 來使得某個數臨時(即無後效性)增大 ,使得這個長度爲 的子區間變爲一個迴文。問每個長度爲 的子區間都做完的最小花費。
題解:
未通過。
H. Safe Path
題意:
給出一個 的點陣,其中 爲起點, 爲終點,所有 周圍 步以內都不能走,問從起點出發能否走到終點。
題解:
先進行一次 預處理,把所有不能走的點都打上標記,然後再進行一次 走迷宮即可。
代碼:
#include <queue>
#include <cstdio>
#include <vector>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=200005;
int n,m,d,sx,sy;
char s[maxn];
vector<char> mp[maxn];
vector<int> vec[maxn];
struct node{
int x,y,ans;
};
vector<node> v;
int dir[4][2]={1,0,-1,0,0,1,0,-1};
bool check(int x,int y){
return x>=0&&x<n&&y>=0&&y<m&&!vec[x][y];
}
void overflow(){
queue<node> q;
for(int i=0,sz=(int)v.size();i<sz;i++){
vec[v[i].x][v[i].y]=1;
q.push(v[i]);
}
while(!q.empty()){
node now=q.front();q.pop();
int x=now.x,y=now.y,tmp=now.ans;
if(tmp==0) continue;
for(int i=0;i<4;i++){
int xx=x+dir[i][0];
int yy=y+dir[i][1];
if(check(xx,yy)){
vec[xx][yy]=1;
q.push(node{xx,yy,tmp-1});
}
}
}
}
int bfs(int x,int y){
if(!check(x,y)) return -1;
vec[x][y]=1;
queue<node> q;
q.push(node{x,y,0});
while(!q.empty()){
node now=q.front();q.pop();
int xx=now.x,yy=now.y;
if(mp[xx][yy]=='F') return now.ans;
for(int i=0;i<4;i++){
int xxx=xx+dir[i][0];
int yyy=yy+dir[i][1];
if(check(xxx,yyy)){
vec[xxx][yyy]=1;
q.push(node{xxx,yyy,now.ans+1});
}
}
}
return -1;
}
int main(){
scanf("%d%d%d",&n,&m,&d);
for(int i=0;i<n;i++){
mp[i].resize(m);
vec[i].resize(m);
scanf("%s",s);
for(int j=0;j<m;j++){
mp[i][j]=s[j];
if(mp[i][j]=='M'){
vec[i].push_back(1);
v.push_back(node{i,j,d});
}else{
vec[i].push_back(0);
}
if(mp[i][j]=='S') sx=i,sy=j;
}
}
overflow();
printf("%d\n",bfs(sx,sy));
return 0;
}
I. Guess the Tree
題意:
交互題。
初始給出一個 ,表示有一顆 個點的完全二叉樹,所以 。現在你要用 次以內的詢問求出每個點的父節點。每次詢問你可以知道兩個點 和 之間的樹上距離。
題解:
分析可得紅色矩陣應當螺旋移動才能使得步數最少,而給定矩陣邊長相同,在紙上模擬直接找到規律即可。
代碼:
未通過
J. Parallelograms
題意:
給出 條木棍,問最多能得到多少平行四邊形。
題解:
先用桶存好每個長度的邊的數量,再把每條邊的數量扔進優先隊列,優先取數量多的,每次拿走兩根,拿兩次意味着多了一個平行四邊形。
寫題解的時候突然想到不用優先隊列,好像直接算就好了。
代碼:
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=200005;
int cnt[maxn];
priority_queue<int> pq;
int main(){
int n;scanf("%d",&n);
for(int i=0;i<n;i++){
int x;scanf("%d",&x);
cnt[x]++;
}
for(int i=1;i<maxn;i++){
if(cnt[i]) pq.push(cnt[i]);
}
int ans=0;
while(true){
if(pq.empty()) break;
int tmp1=pq.top();pq.pop();
if(tmp1<2) break;
tmp1-=2;
if(tmp1>=2) pq.push(tmp1);
if(pq.empty()) break;
int tmp2=pq.top();pq.pop();
if(tmp2<2) break;
tmp2-=2;
if(tmp2>=2) pq.push(tmp2);
ans++;
}
printf("%d\n",ans);
return 0;
}
K. Video Reviews
題意:
有 個人,你需要獲得至少 個人的支持。一個人會支持你可能有兩種條件:
- 你收買了他。
- 在他(第 個人)之前有 個人已經支持你了。
問你做少需要收買多少人。
題解:
二分答案。 時直接遍歷數組檢查是否足夠 人。
代碼:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=200005;
int a[maxn];
int main(){
int n,m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int l=0,r=n,ans=n;
while(l<=r){
int mid=(l+r)/2;
int tmp=0,cnt=0;
for(int i=1;i<=n;i++){
if(cnt>=a[i]) cnt++;
else if(tmp<mid) cnt++,tmp++;
}
if(cnt>=m){
ans=mid;
r=mid-1;
}else{
l=mid+1;
}
}
printf("%d\n",ans);
return 0;
}
L. Queries on a String
題意:
給出一個串 ,進行 次操作,每次操作可以選擇向一個初始爲空的串 的末尾丟進一個字符,每次操作結束後回答當前串 是否時串 的一個子序列。
題解:
序列自動機。每次丟進一個字符後在序列自動機中找到對應的下一個位置,判斷是否能找到,能找到說明可行。
代碼:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=200005;
int nxt[maxn][30],pos[30];
char s[maxn],op[10],ch[5];
int tmp[maxn];
int main(){
scanf("%s",s+1);
int len=(int)strlen(s+1);
for(int i=0;i<26;i++) pos[i]=-1;
for(int i=len;i>=0;i--){
for(int j=0;j<26;j++){
nxt[i][j]=pos[j];
}
pos[s[i]-'a']=i;
}
memset(tmp,-1,sizeof(tmp));
tmp[0]=0;
int now=0;
int q;scanf("%d",&q);
while(q--){
scanf("%s",op);
if(op[1]=='u'){
scanf("%s",ch);
if(tmp[now]==-1||nxt[tmp[now]][ch[0]-'a']==-1) printf("NO\n");
else{
printf("YES\n");
tmp[now+1]=nxt[tmp[now]][ch[0]-'a'];
}
now++;
}else{
tmp[now--]=-1;
if(tmp[now]==-1) printf("NO\n");
else printf("YES\n");
}
}
return 0;
}
M. Forgotten Spell
題意:
給出三個串 ,,。問每個串至多改變一個字符能否使得三個串相等。
題解:
分類討論。
首先找出三個串中有幾個位置三個字符全都不相等,即 ,然後找出三個串中有多少位置三個字符全都相等,即 。
-
若 則必定無解。
-
若 :
-
若 (即只有一個位置全都不相等,其他位置全都相等),則多解(可以將不相等的字符都換成相同的任意字符)。
-
若 (即有一個位置全都不相等,一個位置兩個字符與另一個不相等,其他位置全都相等),此時需要確定哪個位置上某個字符與另兩個不同,得到唯一解。如:
ai bi cj
則此時我們需要把第三個串的 變爲 ,把前兩個串的 和 都變爲 。結果爲:
ci ci ci
-
其他情況無解。
-
-
若 :
把每個兩個字符相同且與第三個字符不相同的位置記錄下來,存放不同的串是第幾個串以及不同的位置。
-
若這樣的位置超過三個則必定無解(需要超過三次修改)。
-
若這樣的位置恰好三個,則考慮爲以下兩種情況中的哪一種:
aab || aaa aca || abb daa || caa
-
第一種是可行的,可以變成以下情況:
aaa aaa aaa
-
第二種不行,因爲第二種對第二個串的操作次數超過了一次。
-
-
若這樣的位置恰好兩個,則考慮爲以下兩種情況的哪一種:
aa || aa aa || ab cc || ca
-
第一種有多解,可以變成以下兩種的任意一種:
ac || ca ac || ca ac || ca
-
第二種有唯一解,可以變成以下情況:
aa aa aa
-
-
代碼:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef pair<int,int> pii;
const int maxn=200005;
char s[3][maxn];
pii p[maxn];
int tot;
void solve(){
int len=(int)strlen(s[0]);
int cnt1=0,cnt2=0;
for(int i=0;i<len;i++){
if(s[0][i]!=s[1][i]&&s[0][i]!=s[2][i]&&s[1][i]!=s[2][i]){
cnt1++;
}
}
for(int i=0;i<len;i++){
if(s[0][i]==s[1][i]&&s[0][i]==s[2][i]&&s[1][i]==s[2][i]){
cnt2++;
}
}
if(cnt1>=2){
printf("Impossible");
}else if(cnt1==1){
if(cnt2==len-1) printf("Ambiguous");
else if(cnt2==len-2){
int a=-1,b=-1,c=-1;
for(int i=0;i<len;i++){
if(s[0][i]!=s[1][i]&&s[0][i]!=s[2][i]&&s[1][i]==s[2][i]){
a=0;b=1;c=2;
break;
}
if(s[1][i]!=s[0][i]&&s[1][i]!=s[2][i]&&s[0][i]==s[2][i]){
a=1;b=0;c=2;
break;
}
if(s[2][i]!=s[0][i]&&s[2][i]!=s[1][i]&&s[0][i]==s[1][i]){
a=2;b=0;c=1;
break;
}
}
for(int i=0;i<len;i++){
if(s[a][i]!=s[b][i]&&s[a][i]!=s[c][i]){
if(s[b][i]!=s[c][i]) printf("%c",s[a][i]);
else printf("%c",s[b][i]);
}else{
printf("%c",s[a][i]);
}
}
}else printf("Impossible");
}else{
for(int i=0;i<len;i++){
if(s[0][i]!=s[1][i]&&s[0][i]!=s[2][i]&&s[1][i]==s[2][i]){
p[tot++]=pii(0,i);
}
if(s[1][i]!=s[0][i]&&s[1][i]!=s[2][i]&&s[0][i]==s[2][i]){
p[tot++]=pii(1,i);
}
if(s[2][i]!=s[0][i]&&s[2][i]!=s[1][i]&&s[0][i]==s[1][i]){
p[tot++]=pii(2,i);
}
}
if(tot>3) printf("Impossible");
else if(tot==3){
if(p[0].first==p[1].first||p[0].first==p[2].first||p[1].first==p[2].first) printf("Impossible");
else{
for(int i=0;i<len;i++){
if(s[0][i]==s[1][i]) printf("%c",s[0][i]);
else if(s[0][i]==s[2][i]) printf("%c",s[0][i]);
else printf("%c",s[1][i]);
}
}
}else if(tot==2){
if(p[0].first==p[1].first) printf("Ambiguous");
else{
for(int i=0;i<len;i++){
if(s[0][i]==s[1][i]) printf("%c",s[0][i]);
else if(s[0][i]==s[2][i]) printf("%c",s[0][i]);
else printf("%c",s[1][i]);
}
}
}else{
printf("Ambiguous");
}
}
printf("\n");
}
int main(){
for(int i=0;i<3;i++) scanf("%s",s[i]);
solve();
return 0;
}