8.13 18级杭电多校第八场

比赛过程

  1006有些考验思维,被卡了很久,这次又犯了三人同时开一道题的错误。其实1008这道模拟和1009的字符串都不算太难。

题解

1003

题意

  给定在同一个圆上的三个点,求一次经过A B C三点的方向是顺时针还是逆时针。

解法

  用矢量叉积求出A在B的左边还是右边,然后再特判一下C是否在A和B中间即可。

代码
int main()
{
    IO;
    int t;
    cin >> t;
    while (t--)
    {
        ll x1,y1,x2,y2,x3,y3;
        cin>>x1>>y1>>x2>>y2>>x3>>y3;
        ll ans1=x1*y2-x2*y1;
        ll ans2=x2*y3-x3*y2;
        ll ans3=x1*y3-x3*y1;
        if(ans1>0)
        {
            if(ans2<0&&ans3>0)
            cout<<"Clockwise\n";
            else cout<<"Counterclockwise\n";
        }
        else
        {
            if(ans2>0&&ans3<0)
            cout<<"Counterclockwise\n";
            else cout<<"Clockwise\n";
        }
    }
}

1004(119/799) Discovery of Cycles

tag:LCT

题意
一张无向图,q个强制在线询问,如果只用区间[l,r]的边,能否组成一个简单环。在线询问。

官方题解
我们可以给每一条边 i 求出以这条边作为左端点, 在最短的区间内能形成环的最小右端点, 标记为 Ri, 如果不存在这样的右端点(即从当前到结尾所有边都不能组成环), 则让 Ri = m + 1 , 显然暴力求这个 Ri 是不允许的。
显然 Ri 一定是递增的, 所以如果我们可以快速删边的话, 那么这个就能快速的求解了.于是就考虑到可以用 Link-Cut-Tree 来优化这个过程, 即只需要维护左端点和右端点, 肯定会尽量拓展右端点, 拓展不了就删掉左端点所在的边然后移动左端点.不断重复这个过程就可以求解所有的 Ri了。时间复杂度 O(m log n + q).

int _, n, m, q, edge[N][2], maxn[N], Stack[N];
struct node {
   int l, r, fa, pre;
   bool flag;
}a[N];

void init() {
   ms(a, 0), ms(edge, 0), ms(maxn, 0), ms(Stack, 0);
}
// 右旋
inline void zig(int x) {
   int y = a[x].fa, z = a[y].fa;
   if(y == a[z].l) a[z].l = x;
   if(y == a[z].r) a[z].r = x;
   a[x].fa = z, a[y].fa = x;
   a[y].l = a[x].r, a[a[x].r].fa = y, a[x].r = y;
}
// 左旋
inline void zag(int x) {
   int y = a[x].fa, z = a[y].fa;
   if(y == a[z].l) a[z].l = x;
   if(y == a[z].r) a[z].r = x;
   a[x].fa = z, a[y].fa = x;
   a[y].r = a[x].l, a[a[x].l].fa = y, a[x].l = y;
}

inline void push(int x) {
   if(!a[x].flag) return;
   a[a[x].l].flag ^= 1;
   a[a[x].r].flag ^= 1;
   swap(a[x].l, a[x].r);
   a[x].flag = 0;
}

inline void Splay(int x) {
   int rt = x;
   for(; a[rt].fa; rt = a[rt].fa) Stack[++Stack[0]] = rt;
   push(rt);
   while(Stack[0]) push(Stack[Stack[0]--]);
   if(rt == x) return;
   a[x].pre = a[rt].pre;
   a[rt].pre = 0;
   while(a[x].fa) {
      int y = a[x].fa, z = a[y].fa;
      if(x == a[y].l) {
         if(y == a[z].l) zig(y);
         zig(x);
      } else {
         if(y == a[z].r) zag(y);
         zag(x);
      }
   }
}

inline void expose(int x) {
   for(int y = 0; x; x = a[x].pre) {
      Splay(x);
      a[a[x].r].fa = 0;
      a[a[x].r].pre = x;
      a[x].r = y;
      a[y].fa = x;
      a[y].pre = 0;
      y = x;
   }
}

inline bool connect(int u, int v) {
   expose(u), Splay(u);
   for(; a[v].fa || a[v].pre; v = a[v].fa ? a[v].fa : a[v].pre);
   return u == v;
}

inline void make_root(int x) {
   expose(x); Splay(x); a[x].flag ^= 1;
}

inline void add(int u, int v) {
   make_root(u);
   a[u].pre = v;
}

inline void cut(int u, int v) {
   make_root(u), expose(v), Splay(v);
   a[a[v].l].fa = 0; a[v].l = 0;
}

int main() {
   for(cin >> _; _--; ) {
      init();
      scanf("%d%d%d", &n, &m, &q);
      rep(i, 1, m) scanf("%d%d", &edge[i][0], &edge[i][1]);
      // 预处理
      for(int i = 1, t = 1; t <= m; ) {
         if(i <= m && !connect(edge[i][0], edge[i][1])) {
            add(edge[i][0], edge[i][1]);
            i++;
         } else {
            maxn[t] = i;
            cut(edge[t][0], edge[t][1]);
            t++;
         }
      }
      int last = 0;
      while(q--) {
         int u, v; scanf("%d%d", &u, &v);
         u = (u ^ last) % m + 1;
         v = (v ^ last) % m + 1;
         if(u > v) swap(u, v);
         if(v >= maxn[u]) last = 1, puts("Yes");
         else last = 0, puts("No");
      }
   }
   return 0;
}

1006

题意

  给出n个区间,要求预测n个数,这n个数分别要在对应区间内,并且要求前后两数相差不超过k。判断能否预测这n个数。

解法

  如果 i 是在 [l, r] 范围内, 那么 i + 1 必须要在 [l − k, r + k] 范围内.这是因为如果 i + 1 选了
范围外的值, i 就无解了.
这样可以从左往右, 把左边的约束带到右边.再从右往左做一遍.最后剩下的区间应该就是可
行域.因为题目只要求一种方案, 全部选最低的即可.复杂度 O(n).
  其实这道题我们当时wa+T+MLE了11次,之后总结为什么会出现这种情况,问题一是没有想到正着来一次后还要反着来一次。问题二是这道题他的数据量真的卡的有点变态,我们起初用来vector里边套pair结果MLE,后来就开始T,T了好久之后发现我们之前都是对n个区间每输入一个区间就进行处理,处理完再输入下一个,这样就会T,但是如果改成先全部输入n个区间,再一一进行处理,这样就能刚好卡过,哭了哭了。

代码
struct pick{
    ll first,second;
}P[maxn];
int main()
{
    IO;
    int t;
    cin >> t;
    while (t--)
    {
        bool flag = 0;
        int n, k;
        cin >> n >> k;
        re(i,0,n-1)
        cin>>P[i].first>>P[i].second;
        re(i, 1, n - 1)
        {
            P[i].first = max(P[i].first, P[i-1].first-k);
            P[i].second =min(P[i].second, P[i-1].second+k);
            if (P[i].first> P[i].second)
            {
                flag = 1;
                cout << "NO\n";
                break;
            }
        }
        if (flag)
            continue;
        de(i, n - 2, 0)
        {
            int lx = P[i + 1].first - k;
            int rx = P[i + 1].second + k;
            int ln = P[i].first;
            int rn = P[i].second;
            ln = max(ln, lx);
            rn = min(rn, rx);
            if (ln > rn)
            {
                flag = 1;
                cout << "NO\n";
                break;
            }
            P[i].first = ln, P[i].second = rn;
        }
        if (flag)
            continue;
        cout << "YES\n";
        re(i, 0, n - 1)
                cout
            << P[i].first << " ";
        cout << endl;
    }
}

1008

题意

  模拟题找规律题,让你一笔画走遍所有的点,每个点只走一次,问你路径。

解法


  把图画出来其实规律不算太难找,对于奇数,上图中第一个边的路径为4242423,第二个边的路径为5353534,会发现其实是全部加一的规律,到6的时候再加就会变成1,注意特判一下最后的边因为要往下一层走,所以最后一个数字不太一样。偶数和奇数只是最后剩下一层还是两层的问题,特判一下就行了。

代码
int main(){
  //IO;
  int t;
  cin>>t;
  while(t--){
    int n;
    cin >> n;
    if(n%2==1){
      for(int i=n;i>1;i-=2){
        int a = 2, b = 3, c = 1;
        FOR(j,1,6){
          a = (a + 1) % 7;
          b = (b + 1) % 7;
          c = (c + 1) % 7;
          if(a==0) a++;
          if(b==0) b++;
          if(c==0) c++;
          if(j!=6){
            cout << a;
            FOR(k,1,i-2) cout << b << c;
          }
          else{
            cout << a;
            FOR(k,1,i-3) cout << b << c;
            cout << b << 4;
          }
        }
      }
    }
    else{
      for(int i=n;i>2;i-=2){
        int a = 2, b = 3, c = 1;
        FOR(j,1,6){
          a = (a + 1) % 7;
          b = (b + 1) % 7;
          c = (c + 1) % 7;
          if(a==0) a++;
          if(b==0) b++;
          if(c==0) c++;
          if(j!=6){
            cout << a;
            FOR(k,1,i-2) cout << b << c;
          }
          else{
            cout << a;
            FOR(k,1,i-3) cout << b << c;
            cout << b << 4;
          }
        }
      }
      int a = 2, b = 3, c = 1;
      FOR(j,1,6){
        a = (a + 1) % 7;
        b = (b + 1) % 7;
        c = (c + 1) % 7;
        if(a==0) a++;
        if(b==0) b++;
        if(c==0) c++;
        if(j!=6){
          cout << a;
        }
        else{
          cout << 3;
        }
      }
    }
    cout << endl;
  }
  return 0;
}

1009

题意

  给出一个串,判断咋个串是否是多个等长的循环同构串的拼接。

解法

  哈希.
枚举 n 的约数 k, 那么你可以 O(n/k) 求出 s1, . . . , sk 的哈希值.同时你可以 O(k) 求出 s1 的循环同构串的哈希值.因此问题转化为, 给出两个数集 S, T, 判断是否有 S ⊆ T.这并不难, 把哈希模数设成 107 级别的, 开一个数组就可以 O(|S| + |T|) 判了.
总复杂度 O(σ(n)C).其中 σ(n) = ∑d|nd, C 为哈希模数个数.
这题其实不难,然而由于我们在1006上花了较多时间,就没有去开这道题,有点小遗憾。这道题的过程中我也T了,最后发现是用map不规范的锅,map这东西数据太大了还是少用的好,自动给你排序太费时间。

代码
const int maxn = 5e6 + 10;
const ll mode = 7000061;
char s[maxn];
ll f[maxn], p[maxn];
ll gethash(int l, int r)
{
	ll ans = (f[r] - f[l - 1] * p[r - l + 1] % mode + mode) % mode;
	return ans;
}
ll mp[maxn<<1];
ll cnt = 0, num[27];
bool check(int n, int len)
{
	cnt++;
	//求所有轮换的hash值
	ll tem = gethash(1, len);
	mp[tem] = cnt;
	re(i, 1, len - 1)
	{
		tem = (tem - p[len - 1] * (ll)(s[i] - 'a' + 1) % mode + mode) % mode;
		tem = tem * 29 % mode;
		tem = (tem + (ll)(s[i] - 'a' + 1)) % mode;
		mp[tem] = cnt;
	}
	//遍历子串
	re(i, 1, n / len - 1)
	{
		ll ans = gethash(i * len + 1, (i + 1) * len);
		if (mp[ans] != cnt)
			return 0;
	}
	return 1;
}
int main()
{
	int t;
	si(t);
	p[0] = 1;
	re(i, 1, maxn - 3) p[i] = p[i - 1] * 29 % mode;
	while (t--)
	{
		int n;
		si(n);
		ms(num, 0);
		ss(s + 1);
		re(i, 1, n)f[i] = (f[i - 1] * 29 % mode + (ll)(s[i] - 'a' + 1)) % mode;
		ll mi = 1e8;
		re(i,1,n)num[s[i]-'a'+1]++;
		re(i, 1, 26)
		{
			if (num[i])
				mi = min(mi, num[i]);
		}
		if (mi == 1)
		{
			pn;
			continue;
		}
		bool ok = 0;
		for (int j = 1; 1ll * j * j <= mi; j++)
		{
			if (mi % j == 0)
			{
				bool flag=0;
				if(j>1&&n%j==0)
				flag = check(n, n / j);
				if (flag)
				{
					ok = 1;
					break;
				}
				int fac = mi / j;
				if (fac == j)
					continue;
				if (fac == 1)
					continue;
				if (n % fac)
					continue;
				flag = check(n, n/fac);
				if (flag)
				{
					ok = 1;
					break;
				}
			}
		}
		if (ok)
			py;
		else
			pn;
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值