2018 Multi-University Training Contest 3
1001:Problem A. Ascending Rating
首先可以通过单调栈维护出每个位置右边第一个大于自己的数所在的位置,记为rightmax[i],然后维护一个双端队列。双端队列里维护[L,L+m-1]中的最长上升子序列,首先队首很好维护,只要判断当前head的
rightmax[]是不是在范围了内就行。队尾每当出队一个元素,需要按rightmax[]递归入队一个上升子序列。
因为每个元素最多只能入队出对一次,所以维护过程是O(2n)
#include<cstdio> #include<cstdlib> #include<cstring> #include<deque> #include<queue> #include<stack> #include<algorithm> #define maxn 10000000+5 using namespace std; struct FastIO { static const int S = 200; int wpos; char wbuf[S]; FastIO() :wpos(0) {} inline int xchar() { static char buf[S]; static int len = 0, pos = 0; if (pos == len) pos = 0, len = fread(buf, 1, S, stdin); if (pos == len) exit(0); return buf[pos++]; } inline int read() { int s = 1, c = xchar(), x = 0; while (c <= 32) c = xchar(); if (c == '-') s = -1, c = xchar(); for (; '0' <= c && c <= '9'; c = xchar()) x = x * 10 + c - '0'; return x * s; } ~FastIO() { if (wpos) fwrite(wbuf, 1, wpos, stdout), wpos = 0; } }io; deque <int> qu; int a[maxn], nxt[maxn], st[maxn], sst[maxn]; int n, m, k, p, q, r, mod; long long ans1, ans2; void find(int now, int lim) { if (nxt[now] != -1 && nxt[now] < lim) find(nxt[now], lim); if (lim != now) qu.push_front(now); } void find2(int now,int lim){ if (nxt[now] != -1 && nxt[now] - lim + 1 <= m) find2(nxt[now], lim); qu.push_front(now); } int main() { int T, top, l, r; T = io.read(); // scanf("%d",&T); while (T--) { n = io.read(); m = io.read(); k = io.read(); p = io.read(); q = io.read(); r = io.read(); mod = io.read(); // scanf("%d%d%d%d%d%d%d",&n,&m,&k,&p,&q,&r,&mod); for (int i = 1; i <= k; i++) a[i] = io.read(); // for (int i = 1; i <= k; i++) scanf("%d", &a[i]); for (int i = k + 1; i <= n; i++) a[i] = ((long long)p*a[i - 1] + (long long)q*i + r) % mod; // memset(nxt,0,sizeof(int)*(n+1)); top = 0; ans1 = ans2 = 0; for (int i = 1; i <= n; i++) { while (top && a[st[top]] < a[i]) nxt[st[top]] = i, top--; st[++top] = i; } while (top) nxt[st[top]] = -1, top--; while (!qu.empty()) qu.pop_back(); qu.push_back(1); int x = 0; while (qu.front() <= n && !qu.empty()) { while (nxt[qu.back()]>0 && nxt[qu.back()] - qu.front() + 1 <= m) { qu.push_back(nxt[qu.back()]); } x = qu.front(); ans1 += a[qu.back()]^x; ans2 += qu.size()^x; qu.pop_front(); if (x == n - m + 1) break; if (!qu.empty()) { int now=x+1, lim = qu.front(), top2=0; if (lim != now) sst[++top2] = now; while (nxt[now] != -1 && nxt[now] < lim) { now = nxt[now]; if (lim != now) sst[++top2] = now; } while (top2) qu.push_front(sst[top2]),top2--; } else { int now = x + 1, lim = x+1, top2 = 0; sst[++top2] = now; while (nxt[now] != -1 && nxt[now] - lim + 1 <= m) { now = nxt[now]; sst[++top2] = now; } while (top2) qu.push_front(sst[top2]),top2--; } } printf("%lld %lld\n", ans1, ans2); } return 0; }
1003: Problem C. Dynamic Graph Matching
状压dp,从大到小状态遍历,如果两个节点都没匹配,就加上方案数,删边时考虑到加边的顺序可以更改,看作最后加的一条边是需要删的边,从而遍历减去影响即可。
#include<cstdio> #include<cstdlib> #include<cstring> #include<deque> #include<queue> #include<stack> #include<algorithm> const int MAX=1e4+5; const int mod=1e9+7; int t,n,m; int a,b,now,f[MAX],cnt[MAX],ans[MAX]; char op; int main(){ int i; scanf("%d",&t); while(t--){ scanf("%d%d",&n,&m); now=1<<n; for(i=0;i<now;i++) cnt[i]=__builtin_popcount(i); memset(f,0,sizeof(f)); f[0]=1; while(m--){ getchar(); scanf("%c%d%d",&op,&a,&b); a--,b--; int S=(1<<a)|(1<<b); if(op=='+'){ for(i=now-1;i>=0;i--) if(!(i&S)) f[S^i]=(f[S^i]+f[i])%mod; } else for(i=0;i<now;i++) if(!(i&S)) f[S^i]=(f[S^i]-f[i]+mod)%mod; memset(ans,0,sizeof(ans)); for(i=0;i<now;i++) ans[cnt[i]]=(ans[cnt[i]]+f[i])%mod; for(i=2;i<=n;i+=2) printf("%d%c",ans[i],i==n?'\n':' '); } } return 0; }
1004:Problem D. Euler Function
打个表吧,很容易看出规律。大于6的数的欧拉函数一定是合数
欧拉函数是积性函数,而除了2,3,质数的欧拉函数是偶数,所以除了个别数,欧拉函数都是合数
#include <cstdio> #include <cstring> #include <algorithm> #include <queue> #include <stack> #include <math.h> #include <vector> using namespace std; //返回某个数的欧拉值 int t,n; int main(){ int i; scanf("%d",&t); while(t--){ scanf("%d",&n); if(n>2) printf("%d\n",n+5); else if(n==1) puts("5"); else if(n==2) puts("7"); } return 0; }
结论题,首先按层将树分成两部分,如果有一部分异或和大于另外一部分,那么先手拿这部分就行了,如果相等,因为是异或所以取一个节点和放一个节点影响一样,所以一定是平局
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #define maxn 100000+5 using namespace std; struct Edge{ int u,v,nxt; }e[maxn<<1]; int head[maxn],w[maxn]; int n,ind,sum,all; void addedge(int x,int y){ e[ind]=(Edge){x,y,head[x]},head[x]=ind++; e[ind]=(Edge){y,x,head[y]},head[y]=ind++; } void dfs(int x,int pa,int dep){ if(dep&1) sum^=w[x]; for(int i=head[x];i!=-1;i=e[i].nxt) if(e[i].v!=pa) dfs(e[i].v,x,dep+1); } int main(){ int T; scanf("%d",&T); while(T--){ memset(head,-1,sizeof(head)); scanf("%d",&n); ind=all=0; for(int i=1;i<=n;i++) scanf("%d",&w[i]),all^=w[i]; for(int i=1;i<n;i++){ int x,y; scanf("%d%d",&x,&y); addedge(x,y); } sum=0; dfs(1,0,1); if((all^sum)==sum) puts("D"); else puts("Q"); } return 0; }
1007: Problem G. Interstellar Travel
由于两点之间移动的代价为叉积,想到转化为面积:把路径从n走回1,得到一个顺时针走的封闭的多边形,该多边形的面积为题中代价的相反数,故要代价最低就要使得该多边形面积最大,即求图中所给点构成的凸包的面积。
由于要字典序最小的路径,首先把所有坐标相同的点只取标号最小的,之后求出凸包后,根据叉积判断某点是否为拐点,两拐点之间同一直线上的点则倒序求出一段递减的标号即可。
1 #include<cstdio> 2 #include<cstring> 3 #include<cstdlib> 4 #include<algorithm> 5 6 #define maxn 200000+5 7 8 using namespace std; 9 10 struct Point{ 11 int x,y,p; 12 Point(){ } 13 Point(int a,int b,int c){ 14 x=a; y=b; p=c; 15 } 16 bool operator < (const Point &T)const{ 17 if(x!=T.x) return x<T.x; 18 if(y!=T.y) return y>T.y; 19 return p<T.p; 20 } 21 bool operator != (const Point &T)const{ 22 if(x!=T.x || y!=T.y) return true; 23 return false; 24 } 25 friend Point operator - (const Point &A,const Point &B){ 26 return Point(A.x-B.x,A.y-B.y,A.p); 27 } 28 }p[maxn],res[maxn],ans[maxn]; 29 30 typedef Point Vector; 31 32 double Cross(const Vector& A, const Vector& B) { return (long long)A.x*B.y - (long long)A.y*B.x; } 33 34 int inp[maxn],lsp[maxn]; 35 int n,t,r; 36 37 int main(){ 38 #ifndef ONLINE_JUDGE 39 freopen("G.in","r",stdin); 40 freopen("G.out","w",stdout); 41 #endif 42 int T; 43 scanf("%d",&T); 44 while(T--){ 45 memset(inp,false,sizeof(inp)); 46 scanf("%d",&n); 47 for(int i=1;i<=n;i++){ 48 scanf("%d%d",&p[i].x,&p[i].y); 49 p[i].p=i; 50 } 51 sort(p+1,p+1+n); 52 t=0; 53 for(int i=1;i<=n;i++){ 54 if(i!=1 && !(p[i]!=p[i-1])) continue; 55 while(t>1 && Cross(res[t]-res[t-1],p[i]-res[t-1])>0) t--; 56 res[++t]=p[i]; 57 } 58 inp[1]=inp[t]=1; 59 for(int i=2;i<t;i++) 60 if(res[i]!=res[i-1] && Cross(res[i]-res[i-1],res[i+1]-res[i])!=0) inp[i]=1; 61 for(int i=t;i>=1;i--) 62 if(inp[i]) lsp[i]=i; 63 else if(res[i].p<res[lsp[i+1]].p) lsp[i]=i; 64 else lsp[i]=lsp[i+1]; 65 66 r=0; 67 for(int i=1;i<=t;i++) 68 if(lsp[i]==i) ans[++r]=res[i]; 69 for(int i=1;i<r;i++) 70 printf("%d ",ans[i].p); 71 printf("%d",ans[r].p); 72 puts(""); 73 } 74 return 0; 75 }
1008:Problem H. Monster Hunter
较为经典的贪心问题。
首先考虑没有树的限制,分两种情况(优先第一种):
1.a<b,先选a较小的那一个
2.a>=b,先选b较大的那一个
在有树的限制的情况下,我们发现这个二元组(a,b)是可以进行合并的,即(a,b)+(c,d) -> (max(a,a-b+c),max(a,a-b+c)+b-a+c-d) ,故当一个节点为其父节点的所有子节点中最优的那个时,我们选完父节点必然会马上选它,故它和它的父亲节点就可以合并,这个操作可以用并查集实现,而每次需要挑出最优的一个节点和它的父节点合并,这个操作可以使用set或者优先队列实现。
(这种贪心方式都适用于有全序关系且可以合并的二元组)
1 #pragma comment(linker, "/STACK:102400000,102400000") 2 3 #include<queue> 4 #include<map> 5 #include<set> 6 #include<cstdio> 7 #include<cstring> 8 #include<cstdlib> 9 #include<algorithm> 10 11 #define maxn (100000+5) 12 13 using namespace std; 14 15 struct Edge { 16 int u, v, nxt; 17 Edge(){} 18 Edge(int x,int y,int z) { 19 u = x; v = y; nxt = z; 20 } 21 }e[maxn << 1]; 22 23 struct Data { 24 long long a, b; 25 Data(){} 26 Data(long long x, long long y) { 27 a = x; b = y; 28 } 29 Data unite(const Data &y)const { 30 long long need = max(a, a - b + y.a); 31 return Data ( need, need + b - a + y.b - y.a ); 32 } 33 friend bool operator <(const Data &x, const Data &y) { 34 if (x.a<x.b && y.a >= y.b) return 0; 35 if (x.a >= x.b && y.a<y.b) return 1; 36 if (x.a<x.b && y.a<y.b) return x.a>y.a; 37 if (x.a >= x.b && y.a >= y.b) return x.b<y.b; 38 } 39 }a[maxn]; 40 41 typedef pair <Data,int> pdi; 42 43 set <pdi> mp; 44 45 int head[maxn], d[maxn], fa[maxn]; 46 int ind, n; 47 int T; 48 49 void addedge(int x, int y) { 50 e[ind] = Edge ( x, y, head[x] ), head[x] = ind++; 51 e[ind] = Edge ( y, x, head[y] ), head[y] = ind++; 52 } 53 54 void init() { 55 mp.clear(); ind = 0; 56 for (int i = 0; i <= n; i++) head[i] = -1; 57 for (int i = 1; i <= n; i++) d[i] = i; 58 } 59 60 void dfs(int x, int p) { 61 for (int i = head[x]; i != -1; i = e[i].nxt) 62 if (e[i].v!=p) { 63 fa[e[i].v] = x; 64 dfs(e[i].v, x); 65 } 66 } 67 68 int find(int x) { 69 if (d[x] == x) return x; 70 return d[x] = find(d[x]); 71 } 72 73 int main() { 74 #ifndef ONLINE_JUDGE 75 freopen("H.in", "r", stdin); 76 freopen("H.out","w",stdout); 77 #endif 78 scanf("%d", &T); 79 while (T--) { 80 scanf("%d", &n); 81 init(); 82 a[1] = Data(0,0); 83 for (int i = 2; i <= n; i++) { 84 long long x, y; 85 scanf("%lld%lld", &x, &y); 86 a[i] = Data(x,y); 87 mp.insert(make_pair(Data(x,y),i)); 88 } 89 for (int i = 1; i<n; i++) { 90 int x, y; 91 scanf("%d%d", &x, &y); 92 addedge(x, y); 93 } 94 fa[1] = 1; 95 dfs(1, 0); 96 while (!mp.empty()) { 97 set<pdi>::iterator it = mp.end(); it--; 98 Data x = it->first, y; 99 int pos = it->second; 100 mp.erase(it); 101 y = a[find(fa[pos])]; mp.erase(make_pair(y,d[fa[pos]])); 102 y = y.unite(x); 103 int i = find(fa[pos]), j = find(pos); 104 d[j] = d[i]; a[d[i]] = y; 105 if(d[i]!=1) mp.insert(make_pair(y,d[i])); 106 } 107 printf("%lld\n",a[1].a); 108 } 109 return 0; 110 }
1009:Problem I. Random Sequence
很有意思的概率DP,官方题解很详细
设f[i][x][y][z]表示考虑前i个位置,a i =x,gcd(a i ,a i−1 )=y,gcd(a i ,a i−1 ,a i−2 )=z的期望,枚举a i+1 的值转移即可。
时间复杂度O(nmS),其中S表示(x,y,z)的状态数。显然合法状态中y∣x,z∣y,当m=100时S=1471。
#include <cstdio> #include <cstdlib> #include <iostream> #include <cstring> #include <algorithm> #include <cmath> #include <queue> #include <vector> #include <string> #include <map> using namespace std; const long long mod=1e9+7; long long dp[2][110][110][110]; int ai[110],vi[110],biao[110][110],le[110],ri[110]; vector <int> fac[110]; int n,m; void init() { memset(dp,0,sizeof(dp)); for(int i=le[3];i<=ri[3];i++) { for(int j=le[2];j<=ri[2];j++) { for(int k=le[1];k<=ri[1];k++) dp[0][i][biao[i][j]][biao[k][biao[i][j]]]++; } } } int gcd(int a,int b) { if(b==0) return a; return gcd(b,a%b); } long long qp(long long p,long long q) { long long cnt=1; while(q>0) { if(q%2==1) cnt=(cnt*p)%mod; q/=2; p=(p*p)%mod; } return cnt%mod; } int main() { int i,j; for(i=1;i<=100;i++) { for(j=1;j<=100;j++) biao[i][j]=gcd(i,j); } for(i=1;i<=100;i++) { for(j=i;j<=100;j+=i) fac[j].push_back(i); } int T; scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); for(i=1;i<=n;i++) { scanf("%d",&ai[i]); if(ai[i]==0) { le[i]=1; ri[i]=m; } else le[i]=ri[i]=ai[i]; } for(i=1;i<=m;i++) scanf("%d",&vi[i]); init(); int xx=0; //cout<<"1"<<endl; for(i=4;i<=n;i++) { int x,y,z; for(x=1;x<=m;x++) { for(vector<int>::iterator iy=fac[x].begin();iy!=fac[x].end();iy++) { y=*iy; for(vector<int>::iterator iz=fac[y].begin();iz!=fac[y].end();iz++) { z=*iz; // cout<<x<<" "<<y<<" "<<z<<endl; if(dp[xx][x][y][z]==0) continue; for(j=le[i];j<=ri[i];j++) { dp[xx^1][j][biao[j][x]][biao[j][y]]+=dp[xx][x][y][z]*vi[biao[j][z]]; dp[xx^1][j][biao[j][x]][biao[j][y]]%=mod; } dp[xx][x][y][z]=0; } } } xx^=1; } long long ans=0; int x,y,z; for(x=1;x<=m;x++) { for(vector<int>::iterator iy=fac[x].begin();iy!=fac[x].end();iy++) { y=*iy; for(vector<int>::iterator iz=fac[y].begin();iz!=fac[y].end();iz++) { z=*iz; // cout<<x<<" "<<y<<" "<<z<<endl; ans=(ans+dp[xx][x][y][z])%mod; } } } long long tmp=qp(m,mod-2); for(i=1;i<=n;i++) { if(ai[i]==0) ans=(ans*tmp)%mod; } printf("%lld\n",ans); } return 0; }
画家算法,模拟一下即可。从下到上,从里到外,从左到右,一个一个的小立方体画就行了
#include <cstdio> #include <cstring> #include <algorithm> #include <queue> #include <stack> #include <math.h> #include <vector> using namespace std; const int MAX=200; int t,a,b,c; int mp[MAX][MAX]; int main(){ int i,j,p,cnt; int x,y; scanf("%d",&t); while(t--){ scanf("%d%d%d",&a,&b,&c); for(i=0;i<MAX;i++) for(j=0;j<MAX;j++) mp[i][j]='.'; x=2*c+1,y=2*b; for(p=0;p<max(b+1,2);p++){ for(i=x;i>x-2*c-1;i-=2) for(j=y;j<y+2*a+1;j+=2) mp[i][j]='+'; x+=2,y-=2; } x=1,y=2*b+1; for(p=0;p<=max(b+1,2);p++){ for(j=y;j<=y+2*a-1;j+=2) mp[x][j]='-'; x+=2,y-=2; } x=2*b+1,y=0; for(p=0;p<c+1;p++){ for(j=1;j<2*a;j+=2) mp[x][j]='-'; x+=2,y-=2; } x=2,y=2*b+2*a,cnt=2; for(p=0;p<max(b+1,2);p++){ for(i=x;i<x+2*c;i+=2) mp[i][y]='|'; x+=2,y-=2; } x=2*b+2,y=0; for(i=x;i<x+2*c;i+=2) for(j=y;j<2*a;j+=2) mp[i][j]='|'; x=2,y=2*b-1; for(p=0;p<b;p++){ for(j=y;j<y+2*a+2;j+=2) mp[x][j]='/'; x+=2,y-=2; } x=2,y=2*b+2*a-1; for(p=0;p<b;p++){ for(i=x;i<x+2*c+2;i+=2) mp[i][y]='/'; x+=2,y-=2; } for(i=1;i<1+2*b+2*c+1;i++){ for(j=0;j<2*b+2*a+1;j++) printf("%c",mp[i][j]); putchar('\n'); } } return 0; }
看到本题自然想到用矩阵乘法来得到走k步由s到t的最短路,记为f[k][s][t],但这样做复杂度不过关。
发现k<=10000,故考虑对这个数组进行分块,设G[s][t]为恰好走1步的最短路,可用矩阵乘法算出恰好走i步的最短路A[i][s][t],
接着再用A[100][s][t]算出恰好走100*i步的最短路B[i][s][t]。然后将A[i][s][t]再进行一次floyd得到至少走i步的最短路。
最后枚举中间点i即可得到ans=min(A[k%100][s][i]+B[k/100][i][t])。
1 #include<cstdio> 2 #include<cstring> 3 #include<cstdlib> 4 #include<algorithm> 5 6 #define maxn 50+5 7 8 using namespace std; 9 10 int n,m,q; 11 12 struct Matrix{ 13 int d[maxn][maxn]; 14 Matrix (){ memset(d,0x3f,sizeof(d)); } 15 void init(){ 16 memset(d,0x3f,sizeof(d)); 17 for(int i=1;i<=n;i++) d[i][i]=0; 18 } 19 Matrix operator * (const Matrix &T)const{ 20 Matrix temp; 21 for(int i=1;i<=n;i++) 22 for(int k=1;k<=n;k++) 23 for(int j=1;j<=n;j++) 24 temp.d[i][k]=min(temp.d[i][k],d[i][j]+T.d[j][k]); 25 return temp; 26 } 27 }a[105],b[105],g,e; 28 29 int main(){ 30 #ifndef ONLINE_JUDGE 31 freopen("M.in","r",stdin); 32 freopen("M.out","w",stdout); 33 #endif 34 int T; 35 scanf("%d",&T); 36 while(T--){ 37 scanf("%d%d",&n,&m); 38 a[0].init(); b[0].init(); g.init(); 39 memset(e.d,0x3f,sizeof(e.d)); 40 for(int i=1;i<=m;i++){ 41 int x,y,z; 42 scanf("%d%d%d",&x,&y,&z); 43 g.d[x][y]=e.d[x][y]=min(e.d[x][y],z); 44 } 45 for(int i=1;i<=100;i++) a[i]=a[i-1]*e; 46 for(int i=1;i<=100;i++) b[i]=b[i-1]*a[100]; 47 for(int k=1;k<=n;k++) 48 for(int i=1;i<=n;i++) 49 for(int j=1;j<=n;j++) 50 g.d[i][j]=min(g.d[i][j],g.d[i][k]+g.d[k][j]); 51 for(int i=0;i<=100;i++) a[i]=a[i]*g; 52 scanf("%d",&q); 53 for(int i=1;i<=q;i++){ 54 int s,t,k,A,B,ans=0x3f3f3f3f; 55 scanf("%d%d%d",&s,&t,&k); 56 A=k%100; B=k/100; 57 for(int j=1;j<=n;j++) 58 ans=min(ans,a[A].d[s][j]+b[B].d[j][t]); 59 if(ans!=0x3f3f3f3f) printf("%d\n",ans); 60 else puts("-1"); 61 } 62 } 63 return 0; 64 }