2017-2018 ACM-ICPC, Asia Daejeon Regional Contest
只打了三个小时。
A. Broadcast Stations
B. Connect3
补题:zz
题解:因为格子是4*4的,而且每次落子的位置最多是只有四个,再加上剪枝,情况不会很多,直接爆搜就行了,再用三进制记录已经合法的情况,去掉重复的情况就行了。(用vs2017交会ac,但c++17会wa1,很奇怪)
//#pragma comment(linker, "/STACK:102400000,102400000") #include<iostream> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<string> #include<math.h> #include<cmath> #include<time.h> #include<map> #include<set> #include<vector> #include<queue> #include<algorithm> #include<numeric> #include<stack> #include<bitset> #include<unordered_map> const int maxn = 0x3f3f3f3f; const double EI = 2.71828182845904523536028747135266249775724709369995957496696762772407663035354594571382178525166427; const double PI = 3.141592653589793238462643383279; //#ifdef TRUETRUE //#define gets gets_s //#endif using namespace std; int a, b; int ans; map<int, int>mp; bool Judge(int c[10][10], int aa, int bb, int pos) { if (aa == a && bb == b && pos == 1) { return false; } int cc[10][10], i, j; for (i = 1; i <= 4; i++) { for (j = 1; j <= 4; j++) { cc[i + 1][j + 1] = c[i][j]; } } aa++; bb++; bool flag = false; if (cc[aa][bb] == cc[aa - 1][bb] && cc[aa][bb] == cc[aa - 2][bb] || cc[aa][bb] == cc[aa + 1][bb] && cc[aa][bb] == cc[aa + 2][bb] || cc[aa][bb] == cc[aa][bb - 1] && cc[aa][bb] == cc[aa][bb - 2] || cc[aa][bb] == cc[aa][bb + 1] && cc[aa][bb] == cc[aa][bb + 2] || cc[aa][bb] == cc[aa + 1][bb + 1] && cc[aa][bb] == cc[aa + 2][bb + 2] || cc[aa][bb] == cc[aa - 1][bb - 1] && cc[aa][bb] == cc[aa - 2][bb - 2] || cc[aa][bb] == cc[aa - 1][bb] && cc[aa][bb] == cc[aa + 1][bb] || cc[aa][bb] == cc[aa][bb - 1] && cc[aa][bb] == cc[aa][bb + 1] || cc[aa][bb] == cc[aa - 1][bb - 1] && cc[aa][bb] == cc[aa + 1][bb + 1] || cc[aa][bb] == cc[aa - 1][bb + 1] && cc[aa][bb] == cc[aa - 2][bb + 2] || cc[aa][bb] == cc[aa + 1][bb - 1] && cc[aa][bb] == cc[aa + 2][bb - 2] || cc[aa][bb] == cc[aa - 1][bb + 1] && cc[aa][bb] == cc[aa + 1][bb - 1]) { flag = true; } if (flag && pos == 2 && aa - 1 == a && bb - 1 == b) { int tmp = 0; for (i = 1;i <= 4;i++) { for (j = 1;j <= 4;j++) { tmp *= 3; tmp += c[i][j]; } } if (!mp[tmp]) { ans++; mp[tmp] = 1; } return false; } if (flag || (!flag && aa - 1 == a && bb - 1 == b)) { return false; } else { return true; } } void f(int c[10][10], int d[10], int pos) { int i; for (i = 1; i <= 4; i++) { if (d[i] <= 4) { c[d[i]][i] = pos; int dd[10][10]; for (int ii = 1;ii <= 4;ii++) { memcpy(dd[ii], c[ii], sizeof(c[ii])); } if (Judge(dd, d[i], i, pos)) { d[i]++; f(dd, d, 3 - pos); d[i]--; } c[d[i]][i] = 0; } } } void init(void) { mp.clear(); } int main(void) { //ios::sync_with_stdio(false); int x; while (~scanf("%d %d %d", &x, &a, &b)) { init(); int c[10][10]; int d[10] = { 1,1,1,1,1,1,1,1,1,1 }; d[x]++; memset(c, 0, sizeof(c)); c[1][x] = 1; ans = 0; f(c, d, 2); printf("%d\n", ans); } return 0; }
C. Game Map
题意:给定一张无向图,要求找出一个最长的序列,使得这个序列的度数是严格递增的。
思路:对于每一个点来说,这个点在序列中的位置可以是1,也可以是从和它相邻的且读书小于它的点转移过来的,所以枚举度数从小到大的点即可。
#include<bits/stdc++.h> #define clr(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; const int maxn=100100; vector<int>ve[100010]; unordered_map<int,int>mp[100010]; int c[100010],de[100010]; struct s { int de,id; }z[100010]; inline bool comp(s a,s b) { return a.de <b.de; } int main(){ int n,m,i,z1,z2,si,j,ans; while(~scanf("%d %d",&n,&m)) { for(i = 0;i <= n;i++) { ve[i].clear(); mp[i].clear(); c[i] = 1; z[i].de = 0; z[i].id = i; de[i] = 0; } for(i = 0;i < m;i++) { scanf("%d %d",&z1,&z2); if(z1 > z2) { int r = z1; z1 = z2; z2 = r; } if(z1 != z2 && !mp[z1][z2]) { mp[z1][z2] = 1; ve[z1].push_back(z2); ve[z2].push_back(z1); z[z1].de++; z[z2].de++; de[z1]++; de[z2]++; } } sort(z,z + n,comp); ans = 1; for(i = 0;i < n;i++) { si = ve[z[i].id].size(); //printf(" %d %d\n",z[i].id,z[i].de); for(j = 0;j < si;j++) { //printf(" %d %d %d\n",z[i].de,z[ve[z[i].id][j]].de,ve[z[i].id][j]); if(z[i].de > de[ve[z[i].id][j]]) { c[z[i].id] = max(c[z[i].id],c[ve[z[i].id][j]] + 1); } } //printf(" %d\n",c[z[i].id]); ans = max(ans,c[z[i].id]); } printf("%d\n",ans); } }
D. Happy Number
#include<iostream> #include<cstring> #include<cmath> #include<cstdlib> #include<cstdio> #include<algorithm> #include<string> #include<map> #include<queue> #include<vector> #include<stack> #define ll long long #define maxn 4001000 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; int n,len; map<int,int> mp; ll read() { ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } void init() { scanf("%d",&n); len=1; mp[n]=1; } void work() { while(1) { int x=n,ans=0; while(x>0) { ans+=(x%10)*(x%10); x/=10; } if(ans==1) { printf("HAPPY"); return; } if(mp[ans]!=0) { printf("UNHAPPY"); return; } else mp[ans]=++len; n=ans; } } int main() { init(); work(); }
E. How Many to Be Happy
补题:kk
一开始想了个假算法,没被队友hack,然后wa3?最近好像经常写假算法
对于这样最小生成树的题的选边问题,我们一定要考虑克鲁斯卡尔的过程,对于一条边权为$w$的边,如果他要在最小生成树上,那么前提条件是权值比他小的边不会把u到v所属的两个结合连接在一起,也就是说,现在我们要去掉一些权值小于$w$的边,让u和v分在不同的两个集合里面,想到这里就会想到最小割的模型,对于每条边,都把权值小于他的边加入到图里,然后求一个最小割就可以了。
所以,最小生成树的题一定要考虑克鲁斯卡尔!这真是一个神奇的算法,还有对数据范围要敏感,$n=100$也应该想到网络流。
#include<bits/stdc++.h> #define clr(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; const ll INFLL = 0x3f3f3f3f3f3f3f3f; const int INF = 0x3f3f3f3f; const int maxn = 510; struct Edge { int to, flow, nxt; Edge(){} Edge(int to, int nxt, int flow):to(to),nxt(nxt), flow(flow){} }edge[maxn << 2]; int head[maxn], dep[maxn]; int S, T; int N, n, m, tot,ans; void Init(int n) { N = n; for (int i = 0; i < N; ++i) head[i] = -1; tot = 0; } void addv(int u, int v, int w, int rw = 0) { edge[tot] = Edge(v, head[u], w); head[u] = tot++; edge[tot] = Edge(u, head[v], rw); head[v] = tot++; } bool BFS() { for (int i = 0; i < N; ++i) dep[i] = -1; queue<int>q; q.push(S); dep[S] = 1; while (!q.empty()) { int u = q.front(); q.pop(); for (int i = head[u]; ~i; i = edge[i].nxt) { if (edge[i].flow && dep[edge[i].to] == -1) { dep[edge[i].to] = dep[u] + 1; q.push(edge[i].to); } } } return dep[T] < 0 ? 0 : 1; } int DFS(int u, int f) { if (u == T || f == 0) return f; int w, used = 0; for (int i = head[u]; ~i; i = edge[i].nxt) { if (edge[i].flow && dep[edge[i].to] == dep[u] + 1) { w = DFS(edge[i].to, min(f - used, edge[i].flow)); edge[i].flow -= w; edge[i ^ 1].flow += w; used += w; if (used == f) return f; } } if (!used) dep[u] = -1; return used; } int Dicnic() { int ans = 0; while (BFS()) { ans += DFS(S, INF); } return ans; } struct node{ int u,v,w; friend bool operator<(const node &a,const node &b) { return a.w<b.w; } }a[maxn]; int main(){ while(cin>>n>>m) { for(int i=1;i<=m;i++) { scanf("%d%d%d",&a[i].u,&a[i].v,&a[i].w); } ans=0; sort(a+1,a+1+m); for(int i=1;i<=m;i++) { Init(n+1); for(int j=1;j<i;j++) { if(a[j].w>=a[i].w)break; addv(a[j].u,a[j].v,1); addv(a[j].v,a[j].u,1); } S=a[i].u,T=a[i].v; ans+=Dicnic(); // printf("debug\n"); } printf("%d\n",ans); } }
F. Philosphoer's Walk
题意:给定一张题目里描述的规模的图,输出走k布后的点。
思路:每一张图都能分解为四张规模一样的图,判断最终会在哪一张小图里和起止点的位置,之后再重复这个操作,直到2*2的图为止。
#include<bits/stdc++.h> #define clr(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; const int maxn=510; struct s { int a,b; }in; int Pow[110]; int Node[5][2] = {0,0,1,1,1,2,2,2,2,1}; inline void init(void) { int i,tmp = 1; for(i = 0;i <= 30;i++) { Pow[i] = tmp; tmp <<= 1; } } inline s f(int num,int step) { int pos,st; s jk; if(num == 1) { s tmp; tmp.a = Node[step][0]; tmp.b = Node[step][1]; return tmp; } pos = (step - 1) / (Pow[num - 1] * Pow[num - 1]); st = (step - 1) % (Pow[num - 1] * Pow[num - 1]) + 1; s nex = f(num - 1,st); if(pos == 0) { jk.a = 0 + nex.b; jk.b = 0 + nex.a; } else if(pos == 1) { jk.a = 0 + nex.a; jk.b = Pow[num - 1] + nex.b; } else if(pos == 2) { jk.a = Pow[num - 1] + nex.a; jk.b = Pow[num - 1] + nex.b; } else { jk.a = Pow[num] + 1 - nex.b; jk.b = Pow[num - 1] + 1 - nex.a; } return jk; } int main(){ int n,m,i; init(); while(~scanf("%d %d",&n,&m)) { for(i = 1;i <= 30;i++) { if(Pow[i] == n) { break; } } in = f(i,m); printf("%d %d\n",in.a,in.b); } }
G. Rectilinear Regions
H. Rock Paper Scissors
题解:FFT,把s1串中石头剪刀布的位置记录下来,再把S2串中石头剪刀布的位置记录下来。分输赢做三次FFT。
#include <cmath> #include <cstdio> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define maxn (1<<18) #define pi 3.141592653589793238462643383 using namespace std; struct complex { double re,im; complex(double r=0.0,double i=0.0) {re=r,im=i;} void print() {printf("%.lf ",re);} } a[maxn*2],b[maxn*2],W[2][maxn*2]; int N,na,nb,rev[maxn*2],n,m; char s1[101010],s2[101010]; double c[maxn*2],ans; complex operator +(const complex&A,const complex&B) {return complex(A.re+B.re,A.im+B.im);} complex operator -(const complex&A,const complex&B) {return complex(A.re-B.re,A.im-B.im);} complex operator *(const complex&A,const complex&B) {return complex(A.re*B.re-A.im*B.im,A.re*B.im+A.im*B.re);} void FFT(complex*a,int f) { complex x,y; for (int i=0; i<N; i++) if (i<rev[i]) swap(a[i],a[rev[i]]); for (int i=1; i<N; i<<=1) for (int j=0,t=N/(i<<1); j<N; j+=i<<1) for (int k=0,l=0; k<i; k++,l+=t) x=W[f][l]*a[j+k+i],y=a[j+k],a[j+k]=y+x,a[j+k+i]=y-x; if (f) for (int i=0; i<N; i++) a[i].re/=N; } void work() { for (int i=0; i<N; i++) { int x=i,y=0; for (int k=1; k<N; x>>=1,k<<=1) (y<<=1)|=x&1; rev[i]=y; } for (int i=0; i<N; i++) W[0][i]=W[1][i]=complex(cos(2*pi*i/N),sin(2*pi*i/N)),W[1][i].im=-W[0][i].im; } void doit() { work(),FFT(a,0),FFT(b,0); for (int i=0; i<N; i++) a[i]=a[i]*b[i]; FFT(a,1); for (int i=0; i<na+nb-1; i++) { c[i]+=a[i].re; if(i>=m-1) ans=max(ans,c[i]); //printf("%d %d\n",i,c[i]); } } void popo() { scanf("%d%d",&n,&m); scanf("%s",s1); scanf("%s",s2); memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); na=n;nb=m; for(int i=0;i<n;i++) if(s1[i]=='R') a[i].re=1; for(int i=0;i<m;i++) if(s2[i]=='P') b[m-i-1].re=1; for (N=1; N<na||N<nb; N<<=1); N<<=1; doit(); //printf("------\n"); memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); for(int i=0;i<n;i++) if(s1[i]=='P') a[i].re=1; for(int i=0;i<m;i++) if(s2[i]=='S') b[m-i-1].re=1; for (N=1; N<na||N<nb; N<<=1); N<<=1; doit(); //printf("------\n"); memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); for(int i=0;i<n;i++) if(s1[i]=='S') a[i].re=1; for(int i=0;i<m;i++) if(s2[i]=='R') b[m-i-1].re=1; for (N=1; N<na||N<nb; N<<=1); N<<=1; doit(); } int main() { popo(); printf("%.lf\n",ans); }
I. Slot Machines
补题kk
我们把这个数组倒过来,现在变成了我们要去掉末尾的一段,使得前面部分循环,并且不完整的循环节在数列的前方。对于两段循环来说,开头一部分肯定是一样的(否则怎么能叫循环呢),如果有一段循环不完整,那么开头一部分肯定是一样的,而这里循环的开头就是倒过来的数组的前缀,后面是一个完整的数组,前缀和后缀相同,我们想到了什么呢?kmp!
但是这里我们只需要$fail$就可以了。
先对倒过来的数组$fail$一遍,得到$f$数组,然后循环长度就变成了$i-f[i]$,对此我们稍微证明一下为什么。
对于$i-f[i]>=f[i]$,也就是相等的前缀和后缀不重叠,那么这必然是一个循环长度。
对于$i-f[i]<f[i]$,也就是相等的前缀喝后缀是重叠的,那么我们就要稍微理解一下,设$len=i-f[i]$那么必然有$s{f[i]->f[i]+len]}==s{f[i]-len->f[i]}$,然后一步一步的画过来,这个也是循环节(这部分还是画图好理解些)
#include<bits/stdc++.h> #define clr(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; const int maxn=1000010; int n; int a[maxn],f[maxn]; void fail(){ f[0]=-1; for(int j=1;j<n;j++) { for(int i=f[j-1];;i=f[i]) { if(a[j]==a[i+1]){ f[j]=i+1; break; }else if(i==-1) { f[j]=-1; break; } } } } int main(){ while(cin>>n) { for(int i=n-1;i>=0;i--) { scanf("%d",&a[i]); } fail(); int k=n,p=1; for(int i=0;i<n;i++) { int kk=n-i-1; int pp=i-f[i]; if(kk+pp<k+p){ k=kk,p=pp; }else if(kk+pp==k+p&&pp<p){ k=kk,p=pp; } } printf("%d %d\n",k,p); } }
J. Strongly Matchable
K. Untangling Chain
L. Vacation Plans