Equal Sentences
思路:因为下标的限制,交换后位置不能和原来超过1。
那么,和后面交换,可以看成后面的和前面的交换。
那么对于每一个位置,只需要看和前面交换即可。
对于没有冲的ABCD类的位置交换组合。
经过递推之后,可以发现就是个斐波那契数列。
当引入了重复后,从只和前面的交换来看。
用dp[i]来表示到i的方案数
首先加入i点,方案为dp[i-1].
如果和前面不重复,那么交换后会形成新的方案,即dp[i-2]
所以转移方程即为dp[i] = dp[i-1] + (s[i] == s[i-1] ? 0 : dp[i-2])
#include<bits/stdc++.h> using namespace std; typedef long long LL; typedef long double ld; typedef pair<int,int> pii; const int N = 2e5+5; const int M = 2e5+5; const LL Mod = 1e9+7; #define rg register #define pi acos(-1) #define INF 1e18 #define INM INT_MIN #define dbg(ax) cout << "now this num is " << ax << endl; inline int read() { int x = 0,f = 1;char c = getchar(); while(c < '0' || c > '9'){if(c == '-') f = -1;c = getchar();} while(c >= '0' && c <= '9'){x = (x<<1)+(x<<3)+(c^48);c = getchar();} return x*f; } LL dp[N]; string t[N]; int main() { int ca;ca = read(); while(ca--) { int n;n = read(); string s;getline(cin,s); memset(dp,0,sizeof(dp)); int len = s.size(),k = 0; string tt = ""; for(int i = 0;i < len;++i) { if(s[i] == ' ') { t[++k] = tt; tt = ""; } else tt += s[i]; } t[++k] = tt; dp[1] = 1,dp[0] = 1; for(int i = 2;i <= n;++i) { dp[i] = dp[i-1]; if(t[i] != t[i-1]) dp[i] = (dp[i]+dp[i-2])%Mod; } printf("%lld\n",dp[n]); } //system("pause"); return 0; }
Deliver the Cake
思路:一开始只是在最短路上进行模改,然后直接跑。
一直TLE。因为可能会多次去改变一个点的最优状态,导致了时间复杂度飙升。
正解:对于L,R这些点,很显然在这个城市上,就应该是哪只手,所以需要考虑的只是在M城市上应该是哪只手。
又因为在M城市上显然只有两种可能,那么进行拆点,对于M城市建立两个点,一个是左手点,一个是右手点。
然后对相邻的点连边,如果不同就加上代价x。
需要注意的是,s和t也可能拆过,所以最优解应该是在min(dis{s1,s2} - {t1,t2})
#include<bits/stdc++.h> using namespace std; typedef long long LL; typedef long double ld; typedef pair<LL,int> pii; const int N = 1e5+5; const int M = 2e5+5; const LL Mod = 1e9+7; #define rg register #define pi acos(-1) #define INF 1e18 #define INM INT_MIN #define dbg(ax) cout << "now this num is " << ax << endl; inline LL read() { LL x = 0,f = 1;char c = getchar(); while(c < '0' || c > '9'){if(c == '-') f = -1;c = getchar();} while(c >= '0' && c <= '9'){x = (x<<1)+(x<<3)+(c^48);c = getchar();} return x*f; } int n,m,s,t,col[N<<1],head[N<<1],cnt = 0; LL x,dis[N<<1]; char ss[N]; struct Node{int to,next;LL dis;}e[M*8]; inline void add(int u,int v,LL w) { e[++cnt].to = v,e[cnt].dis = w,e[cnt].next = head[u],head[u] = cnt; e[++cnt].to = u,e[cnt].dis = w,e[cnt].next = head[v],head[v] = cnt; } void dij(int st) { for(int i = 1;i <= n*2;++i) dis[i] = INF; dis[st] = 0; priority_queue<pii,vector<pii>,greater<pii> > Q; Q.push(pii{0,st}); while(!Q.empty()) { int u = Q.top().second; LL d = Q.top().first; Q.pop(); if(d > dis[u]) continue; for(int i = head[u];i;i = e[i].next) { int v = e[i].to;LL dd = e[i].dis; if(dis[v] > dis[u]+dd) { dis[v] = dis[u]+dd; Q.push(pii{dis[v],v}); } } } } int main() { int ca;ca = read(); while(ca--) { n = read(),m = read(),s = read(),t = read(),x = read(); cnt = 0; memset(head,0,sizeof(head)); scanf("%s",ss); int len = strlen(ss); for(rg int i = 0;i < len;++i) { if(ss[i] == 'L') col[i+1] = -1; else if(ss[i] == 'R') col[i+1] = 1; else col[i+1] = 0; } while(m--) { int u,v; LL w;//小 - 左,大 - 右 u = read(),v = read(),w = read(); if(col[u] != 0 && col[v] == 0) swap(u,v); if(col[u] == 0) { if(col[v] == 0) { add(u,v,w);//左 - 左 add(u+n,v+n,w);//右 - 右 add(u,v+n,w+x);//左 - 右 add(u+n,v,w+x);//右 - 左 } else if(col[v] == -1) { add(u,v,w);//左 - 左 add(u+n,v,w+x);//右 - 左 } else { add(u,v,w+x);//左 - 右 add(u+n,v,w);//右 - 右 } } else { int ma = (col[u] == col[v] ? 0 : x); add(u,v,w+ma); } } LL ans = INF; dij(s); ans = min(ans,min(dis[t],dis[t+n])); if(col[s] == 0) { dij(s+n); ans = min(ans,min(dis[t],dis[t+n])); } printf("%lld\n",ans); } system("pause"); return 0; } /* 1 3 3 1 3 20 MRM 1 2 10 2 3 10 1 3 100 ans - 20 */
Go Running
思路:从坐标轴出发。x为纵坐标,t为横坐标
因为每个人每秒钟只走1的单位距离。
那么可以发现对于每个人的运动坐标,都是k = 1或-1的直线(= 1向东走,-1向西走)
这坐标轴上可见,每个人都有两条直线延伸到的可能。每个人都会被一条直线覆盖到。
当一条直线覆盖到多个人时,这条直线上的人都可能是一个人跑的。
那么显然要最小化选到的直线,让这些直线覆盖到所有点。
如果让点和直线连边,会发现难以实现。因为一个人会连正值k线和负值k线,变得很困难。
那么如果让正值k线和负值k线连边,可以发现就是一个二分图的模型。
我们要选择较少的正k值k线,覆盖了所有的负值k线。那么就是个最小点覆盖的问题。
匈牙利会超时,直接上HK
#include<bits/stdc++.h> using namespace std; typedef long long LL; typedef long double ld; typedef pair<LL,int> pii; const int N = 1e5+5; const int M = 2e5+5; const LL Mod = 1e9+7; #define rg register #define pi acos(-1) #define INF 1e18 #define INM INT_MIN #define dbg(ax) cout << "now this num is " << ax << endl; inline LL read() { LL x = 0,f = 1;char c = getchar(); while(c < '0' || c > '9'){if(c == '-') f = -1;c = getchar();} while(c >= '0' && c <= '9'){x = (x<<1)+(x<<3)+(c^48);c = getchar();} return x*f; } int match[N],tag[N],vis[N],n,cnt1,cnt2; int dx[N],dy[N]; vector<int> G[N]; bool bfs() { memset(dx,0,sizeof(dx)); memset(dy,0,sizeof(dy)); bool flag = false; queue<int> Q; for(int i = 1;i <= cnt1;++i) if(tag[i] == -1) Q.push(i); while(!Q.empty()) { int u = Q.front(); Q.pop(); for(auto v : G[u]) { if(dy[v] != 0) continue; dy[v] = dx[u]+1; if(match[v] == -1) flag = true; else { dx[match[v]] = dy[v]+1; Q.push(match[v]); } } } return flag; } bool Find(int x) { for(auto v : G[x]) { if(vis[v] || dy[v] != dx[x]+1) continue; vis[v] = 1; if(match[v] == -1 || Find(match[v])) { match[v] = x; tag[x] = v; return true; } } return false; } int HK() { memset(tag,-1,sizeof(tag)); memset(match,-1,sizeof(match)); int ans = 0; while(bfs()) { memset(vis,0,sizeof(vis)); for(int i = 1;i <= cnt1;++i) if(tag[i] == -1 && Find(i)) ans++; } return ans; } int main() { int ca;ca = read(); while(ca--) { n = read(); unordered_map<int,int> mp1,mp2; cnt1 = 0,cnt2 = 0; for(int i = 1;i <= n;++i) G[i].clear(); for(rg int i = 1;i <= n;++i) { int t,x;t = read(),x = read(); if(mp1[x+t] == 0) mp1[x+t] = ++cnt1; if(mp2[x-t] == 0) mp2[x-t] = ++cnt2; G[mp1[x+t]].push_back(mp2[x-t]); } printf("%d\n",HK()); } system("pause"); return 0; }
Contest of Rope Pulling
1:歪解:
对两组的人进行01背包。
因为要人数相等才能减到1,所以上界只需要两组人总和较小的。
然后从0开始动态维护上界。最坏复杂度5e8左右。
#include<bits/stdc++.h> using namespace std; typedef long long LL; typedef long double ld; typedef pair<LL,int> pii; const int N = 1e3+5; const int M = 1e6+5; const LL Mod = 1e9+7; #define rg register #define pi acos(-1) #define INF 1e18 #define INM INT_MIN #define dbg(ax) cout << "now this num is " << ax << endl; inline LL read() { LL x = 0,f = 1;char c = getchar(); while(c < '0' || c > '9'){if(c == '-') f = -1;c = getchar();} while(c >= '0' && c <= '9'){x = (x<<1)+(x<<3)+(c^48);c = getchar();} return x*f; } LL w1[1005],v1[1005],w2[1005],v2[1005]; LL dp1[M],dp2[M]; int main() { int ca;ca = read(); while(ca--) { int n,m;n = read(),m = read(); int ma1 = 0,ma2 = 0; for(int i = 1;i <= n;++i) { w1[i] = read(),v1[i] = read(); ma1 += w1[i]; } for(int i = 1;i <= m;++i) { w2[i] = read(),v2[i] = read(); ma2 += w2[i]; } int up = min(ma1,ma2); for(int i = 0;i <= up;++i) dp1[i] = dp2[i] = -INF; dp1[0] = dp2[0] = 0; int tmp1 = 0,tmp2 = 0; for(int i = 1;i <= n;++i) { tmp1 += w1[i]; for(int j = min(tmp1,up);j >= w1[i];--j) dp1[j] = max(dp1[j],dp1[j-w1[i]]+v1[i]); } for(int i = 1;i <= m;++i) { tmp2 += w2[i]; for(int j = min(tmp2,up);j >= w2[i];--j) dp2[j] = max(dp2[j],dp2[j-w2[i]]+v2[i]); } LL ans = -INF; for(int i = 0;i <= up;++i) ans = max(ans,dp1[i]+dp2[i]); printf("%lld\n",ans); } system("pause"); return 0; }
2:正解:
那么,考虑到两组人要w的权值相同。
那么我们可以对第二组人的w取反。
那么第一组人的w值肯定为正,第二组人的w值肯定为负。
那么题目就变成了,找出部分人,w的总和相加为0时,v的最大总值。
我们随机化所有人的位置。来保证w的前缀和一直在0左右浮动,不让这个值过大。
然后就可以进行01背包,以某个数据为中值(这里为40000)作为0的替代点,进行dp
但是这里有点看脸,反正随机了蛮多次都拉胯了