2014嘉杰信息杯ACM/ICPC湖南程序设计邀请赛暨第六届湘潭市程序设计竞赛 解题报告

9 篇文章 0 订阅
3 篇文章 0 订阅

A simple problem

题目大意:求n % 1 + n % 2 + ... + n % n(n≤10^12)。

分析:别被题目名字骗了,这题不算难,但绝对算坑爹。先分析n % k怎么求,从后往前
n % k = n - k, k ∈ [n / 2 + 1, n]
n % k = n - 2k, k ∈ [n / 3 + 1, n / 2]
...
一直照这样求到n / sqrt(n)就行了,剩下的一个循环就好了,时间复杂度O(sqrt(n)),也就是10^6级。坑爹的来了,答案太大,long long也存不下,自己写下大整数吧。因为可以肯定结果不超过10^27,所以我的大整数直接用3个long long表示。代码中的一大半都在写大整数。。。。
#include 
    
    
     
     
#include 
     
     
      
      
#include 
      
      
       
       
#include 
       
       
        
        
#include 
        
        
          #include 
          #include 
          
            #include 
           
             #include 
            
              #include 
             
               #include 
              
                #include 
               
                 #include 
                
                  #include 
                 
                   #include 
                  
                    #include 
                   
                     #define mp make_pair #define X first #define Y second #define MEMSET(a, b) memset(a, b, sizeof(a)) using namespace std; typedef unsigned int ui; typedef long long ll; typedef unsigned long long ull; typedef pair 
                    
                      pii; typedef vector 
                     
                       vi; typedef vi::iterator vi_it; typedef map 
                      
                        mii; typedef priority_queue 
                       
                         pqi; typedef priority_queue 
                        
                          , greater 
                         
                           > rpqi; typedef priority_queue 
                          
                            pqp; typedef priority_queue 
                           
                             , greater 
                            
                              > rpqp; const int MAX_N = 100000 + 2; const ll LL = 1000000000; int a[MAX_N]; struct bigNum { ll bit[3]; bigNum() { } bigNum(const ll &b = 0) { bit[0] = b % LL; bit[1] = b / LL; bit[2] = 0; } void format() { bit[1] += bit[0] / LL; bit[0] %= LL; bit[2] += bit[1] / LL; bit[1] %= LL; } bigNum operator * (const bigNum &bg) const { bigNum tmp(0); for (int i = 0; i < 3; ++i) { for (int j = 0; j <= i; ++j) tmp.bit[i] += bit[j] * bg.bit[i - j]; } tmp.format(); return tmp; } void operator = (const bigNum &bg) { for (int i = 0; i < 3; ++i) bit[i] = bg.bit[i]; } void operator *= (const bigNum &bg) { *this = *this * bg; } bigNum operator + (const bigNum &bg) const { bigNum tmp(0); for (int i = 0; i < 3; ++i) tmp.bit[i] = bit[i] + bg.bit[i]; tmp.format(); return tmp; } void operator += (const bigNum &bg) { *this = *this + bg; } void half() { if (bit[2] % 2) bit[1] += LL; bit[2] /= 2; if (bit[1] % 2) bit[0] += LL; bit[1] /= 2; bit[0] /= 2; } void print() { bool flag = false; if (bit[2]) printf("%I64d", bit[2]), flag = true; if (flag) printf("%09I64d", bit[1]); else if (bit[1]) printf("%I64d", bit[1]), flag = true; if (flag) printf("%09I64d", bit[0]); else printf("%I64d", bit[0]); } }; int main(int argc, char *argv[]) { // freopen("D:\\in.txt", "r", stdin); int t; cin >> t; for (int cas = 1; cas <= t; ++cas) { ll n; scanf("%I64d", &n); bigNum bn(n), ans(0); int k = (int)sqrt((double)n); for (int i = 1; i <= k; ++i) { bigNum tmp1(n / i - n / (i + 1)); bigNum tmp2(n + n - i * (n / i + n / (i + 1) + 1)); tmp1 *= tmp2; tmp1.half(); ans += tmp1; } ll tmp(0); int lmt = (int)(n / (k + 1)); for (int i = 1; i <= lmt; ++i) tmp += n % i; bigNum tt(tmp); ans += tt; printf("Case %d: ", cas); ans.print(); puts(""); } return 0; } 
                             
                            
                           
                          
                         
                        
                       
                      
                     
                    
                   
                  
                 
                
               
              
             
            
           
        
       
       
      
      
     
     
    
    


题目大意:给定一个数列,求出所有子区间的范围(最大值 - 最小值 + 1)之和。

分析:当然不是一个区间一个区间地算,而是把问题分解为(最大值的和) - (最小值的和) + (区间总数 = n(n + 1) / 2)。以最大值之和为例,需要求出以每个a[i]为最大值的区间数目,可以用单调栈,也可以用DP,两者是一样的(参见 另一篇文章),求出从位置i开始向左和向右延伸的最大长度。若以a[i]为最大值的最大区间为[left, right],那么以a[i]为最大值的区间总数为(i - left + 1) * (right - i + 1),即在位置i左右(含i)各取一个数。
需要处理的另一个问题是相同数字。比如序列1 2 1,以两个1为最小值的区间都是[1, 3](下标从1开始),算第一个1时,算到的区间有[1, 1], [1, 2], [1, 3],算第二个1时,算到的区间有[1, 3], [2, 3], [3, 3],也就是说,区间[1, 3]被算了两次。其实算第二个1时应该把最大区间[1, 3]换为[2, 3],2是怎么来的呢?就是上一个1的后一个位置,因为上一个1把端点在它左右的区间全部计算过了,现在只需计算左右端点都在它右边的区间。所以计算前先判断,如果最大区间[left, right]包含了上一个相同数字的位置,就把left换为上一个位置 + 1。这一点确实比较坑爹。
#include 
    
    
     
     
#include 
     
     
      
      
#include 
      
      
       
       
#include 
       
       
        
        
#include 
        
        
          #include 
          #include 
          
            #include 
           
             #include 
            
              #include 
             
               #include 
              
                #include 
               
                 #include 
                
                  #include 
                 
                   #include 
                  
                    #include 
                   
                     #define mp make_pair #define X first #define Y second #define MEMSET(a, b) memset(a, b, sizeof(a)) using namespace std; typedef unsigned int ui; typedef long long ll; typedef unsigned long long ull; typedef pair 
                    
                      pii; typedef vector 
                     
                       vi; typedef vi::iterator vi_it; typedef map 
                      
                        mii; typedef priority_queue 
                       
                         pqi; typedef priority_queue 
                        
                          , greater 
                         
                           > rpqi; typedef priority_queue 
                          
                            pqp; typedef priority_queue 
                           
                             , greater 
                            
                              > rpqp; const int MAX_N = 100000 + 2; int a[MAX_N]; int dp[2][MAX_N]; mii coll; int cnt; int pre[MAX_N]; int cur[MAX_N]; int next[MAX_N]; int mapping(int x) { int tmp = coll[x]; if (!tmp) tmp = coll[x] = ++cnt; return tmp; } int main(int argc, char *argv[]) { // freopen("D:\\in.txt", "r", stdin); int t; cin >> t; for (int cas = 1; cas <= t; ++cas) { coll.clear(); cnt = 0; MEMSET(cur, -1); int n; cin >> n; for (int i = 0; i < n; ++i) { scanf("%d", a + i); int tmp = mapping(a[i]); pre[i] = cur[tmp]; cur[tmp] = i; if (pre[i] != -1) next[pre[i]] = i; next[i] = n; dp[0][i] = dp[1][i] = 1; } for (int i = 1; i < n; ++i) { for (int j = i - 1; j >= 0 && a[j] >= a[i]; dp[0][i] += dp[0][j], j -= dp[0][j]); } for (int i = n - 2; i >= 0; --i) { for (int j = i + 1; j < n && a[j] >= a[i]; dp[1][i] += dp[1][j], j += dp[1][j]); } ll ans = (ll)n * (n + 1) / 2 - (ll)a[0] * dp[1][0]; dp[0][0] = dp[1][0] = 1; for (int i = 1; i < n; ++i) { if (pre[i] <= i - dp[0][i]) ans -= (ll)a[i] * dp[0][i] * dp[1][i]; else ans -= (ll)a[i] * (i - pre[i]) * dp[1][i]; dp[0][i] = dp[1][i] = 1; } for (int i = 1; i < n; ++i) { for (int j = i - 1; j >= 0 && a[j] <= a[i]; dp[0][i] += dp[0][j], j -= dp[0][j]); } for (int i = n - 2; i >= 0; --i) { for (int j = i + 1; j < n && a[j] <= a[i]; dp[1][i] += dp[1][j], j += dp[1][j]); } ans += (ll)a[n - 1] * dp[0][n - 1]; for (int i = n - 2; i >= 0; --i) { if (next[i] >= i + dp[1][i]) ans += (ll)a[i] * dp[0][i] * dp[1][i]; else ans += (ll)a[i] * dp[0][i] * (next[i] - i); } printf("Case %d: %I64d\n", cas, ans); } return 0; } 
                             
                            
                           
                          
                         
                        
                       
                      
                     
                    
                   
                  
                 
                
               
              
             
            
           
        
       
       
      
      
     
     
    
    

题目大意:有一群人要上楼,但电梯不能在相邻楼层停靠,上下一层楼分别消耗能量a, b,问最少消耗的能量。

分析:明显的动态规划,但是考虑到当前状态要受未来状态的影响(假设当前楼层不停,那么到该楼层的人可以选择提前下电梯,也可以选择坐到楼上再走下来,而这与下一层的停靠情况相关),就要把未来的情况考虑进去。用dp[i][0]表示在i层停,dp[i][1]表示在i和i + 1都不停(那么i - 1和i + 2一定都停,因为不可能连续3层都不停),dp[i][2]表示i不停但i - 1和i + 1都停,dp[i][3]表示i - 1和i都不停,总共只有这4种情况。关系很简单,就不说了,关键是这种把未来的影响转移到当前状态的思想。
#include 
    
    
     
     
#include 
     
     
      
      
#include 
      
      
       
       
#include 
       
       
        
        
#include 
        
        
          #include 
          #include 
          
            #include 
           
             #include 
            
              #include 
             
               #include 
              
                #include 
               
                 #include 
                
                  #include 
                 
                   #include 
                  
                    #include 
                   
                     #define mp make_pair #define X first #define Y second #define MEMSET(a, b) memset(a, b, sizeof(a)) using namespace std; typedef unsigned int ui; typedef long long ll; typedef unsigned long long ull; typedef pair 
                    
                      pii; typedef vector 
                     
                       vi; typedef vi::iterator vi_it; typedef map 
                      
                        mii; typedef priority_queue 
                       
                         pqi; typedef priority_queue 
                        
                          , greater 
                         
                           > rpqi; typedef priority_queue 
                          
                            pqp; typedef priority_queue 
                           
                             , greater 
                            
                              > rpqp; const int MAX_N = 100000 + 2; int cnt[MAX_N]; int dp[MAX_N][5]; int main(int argc, char *argv[]) { // freopen("D:\\in.txt", "r", stdin); int t; cin >> t; for (int cas = 1; cas <= t; ++cas) { int n, m, a, b; MEMSET(cnt, 0); cin >> n >> m >> a >> b; while (m--) { int x; scanf("%d", &x); ++cnt[x]; } for (int i = 2; i < n; ++i) { dp[i][0] = min(dp[i - 1][2], dp[i - 1][3]); if (i <= n - 2) { dp[i][1] = dp[i - 1][0] + min(a + a, b) * cnt[i]; } dp[i][2] = dp[i - 1][0] + min(a, b) * cnt[i]; dp[i][3] = dp[i - 1][1] + min(a, b + b) * cnt[i]; } dp[n][0] = min(dp[n - 1][2], dp[n - 1][3]); int ans = min(dp[n][0], dp[n - 1][0] + cnt[n] * b); printf("Case %d: %d\n", cas, ans); } return 0; } 
                             
                            
                           
                          
                         
                        
                       
                      
                     
                    
                   
                  
                 
                
               
              
             
            
           
        
       
       
      
      
     
     
    
    

分析:简单题。

题目大意:从一个倒三角里面选取m个数使得其和最大,要求是第一行可任意选取,其余各行在选取之前必须把上一行左上角和右上角两个数都选取。

分析:很经典的斜对角DP,和打砖块一模一样(HNOI 2004 打砖块,去网上搜一下吧,可以找到一份PPT,讲得很详细),关键是图的转换。
#include 
    
    
     
     
#include 
     
     
      
      
#include 
      
      
       
       
#include 
       
       
        
        
#include 
        
        
          #include 
          #include 
          
            #include 
           
             #include 
            
              #include 
             
               #include 
              
                #include 
               
                 #include 
                
                  #include 
                 
                   #include 
                  
                    #include 
                   
                     #define mp make_pair #define X first #define Y second #define MEMSET(a, b) memset(a, b, sizeof(a)) using namespace std; typedef unsigned int ui; typedef long long ll; typedef unsigned long long ull; typedef pair 
                    
                      pii; typedef vector 
                     
                       vi; typedef vi::iterator vi_it; typedef map 
                      
                        mii; typedef priority_queue 
                       
                         pqi; typedef priority_queue 
                        
                          , greater 
                         
                           > rpqi; typedef priority_queue 
                          
                            pqp; typedef priority_queue 
                           
                             , greater 
                            
                              > rpqp; const int MAX_N = 50; const int MAX_M = 500; int a[MAX_N][MAX_N]; int dp[MAX_N][MAX_N][MAX_M]; int sum[MAX_N][MAX_N]; int main(int argc, char *argv[]) { // freopen("D:\\in.txt", "r", stdin); int t; cin >> t; for (int cas = 1; cas <= t; ++cas) { int n, m; cin >> n >> m; //注意i, j的取值 for (int j = 1; j <= n; ++j) { for (int i = j; i <= n; ++i) cin >> a[i][j]; } for (int i = 1; i <= n; ++i) { for (int j = 1; j <= i; ++j) sum[i][j] = sum[i][j - 1] + a[i][j]; } MEMSET(dp, 0X99); dp[0][0][0] = 0; for (int i = 1; i <= n; ++i) { dp[i][1][1] = a[i][1]; dp[i][0][0] = 0; } int ans(0); for (int i = 1; i <= n; ++i) { for (int k = 0; k <= m; ++k) { for (int j = 0; j <= i && j <= k; ++j) { for (int p = max(j - 1, 0); p <= i - 1; ++p) { dp[i][j][k] = max(dp[i][j][k], dp[i - 1][p][k - j] + sum[i][j]); } if (dp[i][j][k] > ans) ans = dp[i][j][k]; } } } printf("Case %d: %d\n", cas, ans); } return 0; } 
                             
                            
                           
                          
                         
                        
                       
                      
                     
                    
                   
                  
                 
                
               
              
             
            
           
        
       
       
      
      
     
     
    
    

分析:水题。

分析:很简单,打表就行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值