2019杭电多校第一场

1001 Blank

题意: 求长度为 n , n ≤ 100 n,n \leq 100 n,n100,满足 [ l , r ] [l,r] [l,r] 中有 x x x个不同元素的数组的个数 ( 1 ≤ x ≤ 4 ) (1\leq x\leq4) (1x4)
分析: 注意到数组的范围很小,容易联想到dp来做,关键是怎么确定状态呢?
考虑到计数问题填每一个位置的方案数以及题目 [ l , r ] [l,r] [l,r]的限制,可以考虑维护状态这四个元素最后一次出现的位置 d p [ j ] [ k ] [ l ] [ i ] , j &lt; k &lt; l &lt; i dp[j][k][l][i],j &lt; k &lt; l &lt; i dp[j][k][l][i],j<k<l<i 即可,有一个小trick就是可以用滚动数组,因为有一维已经确定了,肯定是 i − 1 i-1 i1
d p [ l ] [ k ] [ i − 1 ] [ i ] = d p [ l ] [ k ] [ i − 1 ] [ k ] + d p [ j ] [ l ] [ k ] [ i − 1 ] dp[l][k][i-1][i] = dp[l][k][i-1][k]+dp[j][l][k][i-1] dp[l][k][i1][i]=dp[l][k][i1][k]+dp[j][l][k][i1]
d p [ j ] [ k ] [ i − 1 ] [ i ] = d p [ j ] [ k ] [ i − 1 ] [ i ] + d p [ j ] [ l ] [ k ] [ i − 1 ] dp[j][k][i-1][i] = dp[j][k][i-1][i] + dp[j][l][k][i-1] dp[j][k][i1][i]=dp[j][k][i1][i]+dp[j][l][k][i1]
d p [ j ] [ l ] [ i − 1 ] [ i ] = d p [ j ] [ l ] [ i − 1 ] [ i ] + d p [ j ] [ l ] [ k ] [ i − 1 ] dp[j][l][i-1][i] = dp[j][l][i-1][i] +dp[j][l][k][i-1] dp[j][l][i1][i]=dp[j][l][i1][i]+dp[j][l][k][i1]
d p [ j ] [ k ] [ l ] [ i ] = d p [ j ] [ k ] [ l ] [ i ] + d p [ j ] [ k ] [ l ] [ i − 1 ] dp[j][k][l][i] = dp[j][k][l][i]+dp[j][k][l][i-1] dp[j][k][l][i]=dp[j][k][l][i]+dp[j][k][l][i1]

参考代码:


typedef pair<int,int> P;
const int maxn = 100+2;
const int N = maxn;
int dp[2][N][N][N];
int (*f)[N][N] = dp[0];
int (*g)[N][N] = dp[1];
vector<P> G[maxn];
// int A[maxn];
inline void Add(int &a,int b){
    a += b;
    if(a >= mod)
        a -= mod;
}
void solve(){
    int n,m;
    scanf("%d%d",&n,&m);
    // memset()
    for(int i = 1;i <= n;++i)
        {
            G[i].clear();
        }
    while(m--){
        int r,l,num;
        scanf("%d%d%d",&l,&r,&num);
        G[r].Pb(P(l,num));
    }
    f[0][0][0] = 1;
    for(int i = 1;i<= n; ++i){
        swap(g,f);
        for(int j = 0;j < i; ++j){
            for(int k = j;k < i; ++k){
                for(int l = k;l < i; ++l)
                {
                    int &a = g[j][k][l];
                    if(a){
                        Add(f[k][l][i-1],a);
                        Add(f[j][l][i-1],a);
                        Add(f[j][k][i-1],a);
                        Add(f[j][k][l],a);
                    }
                    g[j][k][l] = 0;
                }
            }
        }
        for(int j = 0;j < i; ++j){
            for(int k = j;k < i; ++k){
                for(int l = k; l < i; ++l){
                    for(auto &c:G[i]){
                        int t = 0;
                        t += i >= c.first;
                        t += j >= c.first;
                        t += k >= c.first;
                        t += l >= c.first;
                        // cout<<t<<endl;
                        if(t != c.second) f[j][k][l] = 0;
                    }
                }
            }
        }
    }
    int ans = 0;
    for(int i = 0;i < n; ++i){
        for(int j = i;j < n; ++j){
            for(int k = j;k < n; ++k){
                Add(ans,f[i][j][k]);
                f[i][j][k] = 0;
            }
        }
    }

    cout<<ans<<endl;
}
int main(void)
{
    int T;cin>>T;
    while(T--){
        solve();
    }
    

   return 0;
}

1002 Operation

题意:
有两种操作:
0 , l , r 0,l,r 0,l,r 查询 [l,r] 中数能异或出来的最大值
1 , x 1,x 1,x 在末尾插入一个新的x
分析:
由于不是连续的,所以不能用trie树来做,只能通过线形基,考虑对每一个位置贪心的维护一个线形基,同时维护位置,使得位置尽量靠右即可

参考代码

const int maxn = 1e6+10;
int a[maxn][31],loc[maxn][31];
void ins(int x,int p){
	memcpy(a[x],a[x-1],sizeof(a[x]));
	memcpy(loc[x],loc[x-1],sizeof(loc[x]));
	int xx = x;
	// cout<<p<<endl;
	for(int i = 30;i >= 0; --i){
		// cout<<x<<" "<<i<<" "<<p<<endl;
		if((1<<i)&p){
			if(!a[x][i]){
				a[x][i] = p;
				loc[x][i] = xx;
				return ;
			}
			if(loc[x][i] < xx){
				swap(a[x][i],p);
				swap(loc[x][i],xx);
			}
			p ^= a[x][i];
		}
	}
}

int  query(int l,int r){
	int ans = 0;
	for(int i = 30; i >= 0; --i){
		if(a[r][i] && loc[r][i] >= l)
			ans = max(ans,ans ^ a[r][i]);
		// cout<<r<<" "<<i<<" "<<a[r][i]<<" "<<loc[r][i]<<endl;
	}
	return ans;

}
int A[maxn];
int main(void)
{
    int n,m;
    int T;
    cin>>T;

    while(T--){
    	cin>>n>>m;
    	memset(a,0,sizeof(a));
    	for(int i = 1;i <= n; ++i){
    		scanf("%d",&A[i]);
    		ins(i,A[i]);
    	}
    	int lastans = 0;
    	while(m--){
    		int op,x,l,r;
    		scanf("%d",&op);
    		if(op ==0){
    			scanf("%d%d",&l,&r);
    			l = (l^lastans)%n+1;
    			r = (r^lastans)%n+1;
    			if(l > r)
    				swap(l,r);
    			printf("%d\n",lastans = query(l,r));

    		}
    		else{
    			scanf("%d",&x);
    			++n;
    			x ^= lastans;
    			ins(n,x);
    		}
    	}
    }


   return 0;
}

1004 vocation

题意:
给定n+1辆车的最大速度,长度,相对于终点初始位置,求最后一辆车通过终点的时间
分析:
对于最后一辆车,考虑它最后和谁合体的最前面那一辆车,这辆车没有与其他车辆相撞,所以他的到达终点的速度和时间已知,最后一辆车到达的时间正好是这辆车的速度跑完中间所有车的长度
参考代码

while(cin>>n){
    for(int i = 0;i <= n; ++i)
      scanf("%d",&l[i]);
    for(int i = 0;i <= n; ++i)
      scanf("%d",&s[i]);
    for(int i = 0;i <= n; ++i)
      scanf("%d",&v[i]);
    double ans = 1.0*s[0]/v[0];
    double sum = 0.0;
    for(int i = 1;i <= n; ++i){
      ans = max(ans,1.00*(s[i]+(sum+=l[i]))/v[i]);
    }
    printf("%.10f\n",ans);
   }
    

1005 Path

题意: 有一个n个点和m条边的有向图,毁坏一条边的代价是它的边权,问使得从 1 − &gt; n 1-&gt;n 1>n的最短路增大需要的最少代价是多少
分析: 考虑抠出来所有的最短路上的边,然后跑求最小割即可,复杂度玄学
参考代码

1010 string

题意:有一个字符串长度为n,求一个长度为k的子序列k,每一个字符的数量有下界L和上界R, 求一个字典序最小的
分析: 字典序最小的子序列通常都是枚举每一位放什么即可

const int maxn = 1e5 + 10;
char ar[maxn];
int used[maxn];
int nxt[maxn][26];
vector<int> vec[26];
int head[26],L[26],R[26];
int k;
void solve() {
  for(int i = 0;i < 26; ++i)
    scanf("%d%d",&L[i],&R[i]);
  for (int i = 0; i < 26; ++i)
    vec[i].clear();
  me(used),me(head);
  int n = strlen(ar);
  me(nxt[n]);
  for (int i = n - 1; i >= 0; --i) {
    for (int j = 0; j < 26; ++j)
      nxt[i][j] = nxt[i + 1][j];
    nxt[i][ar[i] - 'a']++;

  }
  for (int i  = 0; i < n; ++i)
    vec[ar[i] - 'a'].push_back(i);
  // DEBUG;
  int now = -1;
  vector<int> v;
  for (int i = 0; i < k; ++i) {
    // cout<<i<<endl;
    bool yes = 0;
    for (int j = 0; j < 26; ++j) { // 枚举这一位放什么
      bool flag = true;
      while (head[j] < (int)vec[j].size() && vec[j][head[j]] < now)
        {
          head[j]++;
          // cout<<j<<endl;
        }

      if(head[j] == vec[j].size()) continue;
      if(used[j] >= R[j]) continue;
      int pos = vec[j][head[j]];
      int sum = 0;
      used[j]++;
      for(int k = 0;k < 26; ++k)
        sum += max(L[k]-used[k],0);
      if(sum > k-i-1)
        flag = false;
      for(int i = 0;i < 26; ++i)
        if(nxt[pos+1][i] < L[i]-used[i])
          flag = false;

      if(!flag)
        used[j]--;
      else
      {
        yes = true;
        now = pos;
        head[j]++;
        v.push_back(now);
        break;
      }

    }
    if(!yes){
      puts("-1");
      return ;
    }
    // else
      // now = pos;
  }

  for(auto c: v)
    putchar(ar[c]);
  puts("");
}
int main(void)
{
  while (~scanf("%s%d", ar, &k)) {
    solve();
  }


  return 0;
}

1011 Function

题意:

在这里插入图片描述
分析

  1. 考虑将三次根号去掉,分块 [ 1 , 2 3 − 1 ] , [ 2 3 , 3 3 − 1 ] , . . . . [ i 3 , i 3 − 1 ] . . . , [ j 3 , n ] [1,2^3-1],[2^3,3^3-1],....[i^3,i^3-1]...,[j^3,n] [1,231],[23,331],....[i3,i31]...,[j3,n],
  2. 每一块的大小 ( i + 1 ) 3 − 1 − i 3 + 1 = 3 ∗ i 2 + 3 ∗ i + 1 = i ( 3 i + 3 ) + 1 (i+1)^3-1-i^3+1 = 3*i^2+3*i+1 = i(3i+3)+1 (i+1)31i3+1=3i2+3i+1=i(3i+3)+1,其中每i个数和i的 g c d s u m gcd_{sum} gcdsum都相同(辗转相除法),1特判为第一个 g c d ( i 3 , i ) = i gcd(i^3,i) = i gcd(i3,i)=i即可,
  3. 怎么求 g ( n ) = ∑ i = 1 i = n g c d ( i , n ) g(n) = \sum_{i= 1}^{i = n}gcd(i,n) g(n)=i=1i=ngcd(i,n),我们发现 g ( n ) g(n) g(n)符合积性函数的性质, g ( p i ) = ( i + 1 ) ∗ p i + p i − 1 g(p^i)=(i+1)*p^i+p^{i-1} g(pi)=(i+1)pi+pi1,线性筛预处理即可
  4. 考虑最后一块 ∑ j = i 3 n g c d ( j , i ) \sum_{j = i^3}^{n}gcd(j,i) j=i3ngcd(j,i),对其按照i进行分块,不足一块的部分 ≤ 1 e 7 \leq1e7 1e7, 可以利用欧拉函数暴力算出来。

代码参考

https://github.com/Strive-for-excellence/Training/blob/master/2019%20Multi-University%20Training%20Contest%201/1011.cpp

1013 Code

题意: 询问是否存在一条直线将黑白点分开
分析: 对黑白点分别求凸包,然后特判只有一个点,两个点的情况,具体实现看代码
代码参考


https://github.com/Strive-for-excellence/Training/blob/master/2019%20Multi-University%20Training%20Contest%201/1013.cpp
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值