【暑假】[深入动态规划]UVa 10618 Fun Game

本文解析了UVa10618趣题,通过寻找最短序列来确保所有给定序列均为其连续子序列。讨论了序列的方向不确定性、环形特性及可能的多次循环问题,并提供了详细的C++实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

UVa 10618 Fun Game

 

题目:

  http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=36035

思路:

  一圈人围坐,给出小球的传递序列,求解最少有多少个人。

  问题简单化:如果有一排人,单向传递,给出序列求解最少多少人。那么问题就是:求解一个最短序列使得给出的所有序列都是该序列的连续子序列。

  再分别解决以下问题:

  1.     第一个人可以向左向右传递: 每个传递序列有两种情况,分别是正序与逆序。因为不清楚当前序列是向左传还是向右传。
  2.     传递是一个环:规定将第一个序列正向串作为首序列,累计答案的时候只需要算上重叠部分即可。
  3.     输入可能是绕过好几圈的情况:如果绕过好几圈,那么其他的字串因已经被包含而舍弃,dp过程不会进行,最后累计ans时ans=len[0]-重叠(s[0][0],s[0][0])即最小循环节的长度。
  4.     输入保证n>=2:当ans<=1是修改为2

代码:

 

 1 #include<iostream>
 2 #include<cstring>
 3 #include<vector>
 4 #include<algorithm>
 5 #define FOR(a,b,c) for(int a=(b);a<(c);a++)
 6 using namespace std;
 7 
 8 const int maxn = 16;
 9 const int maxlen= 100 + 5;
10 
11 struct Node{
12     string s,rev;
13     bool operator <(const Node& rhs) const{
14       return s.size()<rhs.s.size();      //size由小到大 
15     }
16 };
17 
18 int n;
19 int d[1<<maxn][maxn][2];
20 int overlap[maxn][maxn][2][2];
21 string s[maxn][2];
22 int len[maxn];
23 
24 //return a右 && b左的重叠部分 
25 int make_overlap(const string& a,const string& b){
26     int n1=a.size();
27     int n2=b.size();
28     FOR(i,1,n1) {    //找到最小重合点 即返回最大重合长度 
29         if(n2 + i <= n1) continue;
30         bool ok=true;
31         for(int j=0;i+j<n1;j++) 
32           if(a[i+j] != b[j]) { ok=false; break; }
33         if(ok) return n1-i;
34     } 
35     return 0;  //无重合 
36 }
37 
38 void init() {
39     Node nodes[maxn];
40     FOR(i,0,n) {
41         cin>>nodes[i].s;
42         nodes[i].rev=nodes[i].s;
43         reverse(nodes[i].rev.begin(),nodes[i].rev.end());  //反转串 
44     }
45     
46     sort(nodes,nodes+n);    //sort 
47     int nn=0;     
48     FOR(i,0,n) {
49         bool need=true;
50         FOR(j,i+1,n) 
51          if(nodes[j].s.find(nodes[i].s) != string::npos ||
52           nodes[j].rev.find(nodes[i].s) != string::npos){  //如果被包含则舍弃 
53              need=false; break;
54          }
55         if(need) {
56             s[nn][0]=nodes[i].s;
57             s[nn][1]=nodes[i].rev;
58             len[nn]=s[nn][0].size();
59             nn++;
60         }
61     }
62     n=nn;
63     
64     //构造重叠数组 
65     FOR(i,0,n) FOR(x,0,2)
66      FOR(j,0,n) FOR(y,0,2)
67       overlap[i][j][x][y]=make_overlap(s[i][x],s[j][y]);
68 }
69 
70 inline void update(int& x,int v) {
71     if(x<0 || v<x) x=v;
72 }
73 
74 void solve() {
75     memset(d,-1,sizeof(d));
76     d[1][0][0]=len[0];  //始终把s[0][0]放在首位 
77     
78     int full=1<<n;
79     FOR(s,1,full) 
80       FOR(i,0,n) FOR(x,0,2) if(d[s][i][x]>=0) //已求过集合中的枚举 
81        FOR(j,1,n) if(!((1<<j) & s)) FOR(y,0,2)  //刷表更新的目标枚举 
82         update(d[s|(1<<j)][j][y],  d[s][i][x] + len[j]-overlap[i][j][x][y]);
83         
84     int ans=-1; full--;
85     FOR(i,0,n) FOR(x,0,2) { 
86       if(d[full][i][x]<0) continue; 
87       update(ans, d[full][i][x] - overlap[i][0][x][0]);  //ans累计 枚举尾字串并减去与第一个串的重叠部分 
88     } 
89     if(ans<=1) cout<<"2\n";       //题目中明确给出 n>=2 
90     else 
91      cout<<ans<<"\n";
92 }
93 int main(){
94     while(cin>>n && n) {
95         init();
96         solve();
97     }
98     return 0;
99 }

 

### 关于UVa 307问题的动态规划解法 对于UVa 307 (Sticks),虽然通常采用深度优先搜索(DFS)+剪枝的方法来解决这个问题,但也可以尝试构建一种基于动态规划的思想去处理它。然而,在原描述中并未提及具体的动态规划解决方案[^3]。 #### 动态规划解题思路 考虑到本题的核心在于通过若干根木棍拼接成更少数量的新木棍,并使得这些新木棍尽可能接近给定的目标长度。为了应用动态规划技术,可以定义一个二维数组`dp[i][j]`表示从前i种不同类型的木棍中选取一些组合起来能否恰好组成总长度为j的情况: - 如果存在这样的组合,则`dp[i][j]=true`; - 否则`dp[i][j]=false`. 初始化时设置`dp[0][0]=true`, 表明没有任何木棍的情况下能够构成零长度。接着遍历每种类型的木棍以及所有可能达到的累积长度,更新对应的布尔值。最终检查是否存在某个k使得`sum/k * k == sum && dp[n][sum/k]`成立即可判断是否能成功分割。 这种转换方式利用了动态规划中的两个重要特性:最优化原理和重叠子问题属性。具体来说,每当考虑一根新的木棍加入现有集合时,只需要关注之前已经计算过的较短长度的结果,从而避免重复运算并提高效率[^1]. #### Python代码实现 下面给出一段Python伪代码用于说明上述逻辑: ```python def can_partition_sticks(stick_lengths, target_length): n = len(stick_lengths) # Initialize DP table with False values. dp = [[False]*(target_length + 1) for _ in range(n + 1)] # Base case initialization. dp[0][0] = True for i in range(1, n + 1): current_stick = stick_lengths[i - 1] for j in range(target_length + 1): if j >= current_stick: dp[i][j] |= dp[i - 1][j - current_stick] dp[i][j] |= dp[i - 1][j] return any(dp[-1][l] and l != 0 for l in range(target_length + 1)) # Example usage of the function defined above. stick_lengths_example = [...] # Input your data here as a list. total_sum = sum(stick_lengths_example) if total_sum % min(stick_lengths_example) == 0: result = can_partition_sticks(stick_lengths_example, int(total_sum / min(stick_lengths_example))) else: result = False print('Can partition sticks:', 'Yes' if result else 'No') ``` 需要注意的是,这段代码只是一个简化版本,实际比赛中还需要进一步调整参数以适应特定输入范围的要求。此外,由于题目本身允许有多余的小段剩余未被使用,所以在设计状态转移方程时也应适当放宽条件.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值