牛客多校第四场题解

A. 换根DP

  • 题目: meeting

  • 链接:https://ac.nowcoder.com/acm/contest/884/A

  • 大意:从一棵树(n=1e5)上,选一个点,使这个点到其他点的距离的最大值最小

  • 分析:
    2种做法:

    • 树形dp,暴力dp,换根dp
      • 这个做法大家都能想到就是不好写,换根dp写起来太麻烦了,所以后来我弄了个换根dp的板子,因为在dp的时候更新信息其实都是一样的,有重复的地方,把东西记录下来之后,就会发现换根dp只有更新信息的部分是有变化的,如果能够专注在更新这块的信息上,就会觉得换根dp很好写。
    #include<bits/stdc++.h>
    #define F(i,a,b) for(ll i=(a);i<=ll(b);++i)
    using namespace std;
    typedef long long ll;
    const int inf = 0x3f3f3f3f;
    const int maxn = int(1e5+9);
    struct edge
    {
        int f, t;
    };
    struct info
    {
        int f, t;
        int len;
        bool operator<(const info&ri)const
        {
            return len > ri.len;
        }
    };
    vector<info>H[maxn];
    vector<edge>G[maxn];
    int in[maxn], k, ans[maxn];
    void upd(int from,int to)
    {
        int ma = -inf;
        int cnt(0);
        F(i, 0, H[from].size() - 1)if (H[from][i].f != to)
        {
            ++cnt;
            if (cnt == 1)
            {
                ma = H[from][i].len+1;
                break;
            }
        }
        if (ma != -inf)
        {
            H[to].push_back({ from,to,ma });
            sort(H[to].begin(), H[to].end());
            if (H[to].size() >= 3)H[to].pop_back();
        }
    }
    int dfs1(int u,int p)
    {
        if (in[u])
        {
            H[u].push_back({ u,u,0 });
        }
        for (auto &e : G[u])if(e.t!=p)
        {
            int v = e.t;
            dfs1(v,u);
        }
        //sort(H[u].begin(), H[u].end());
        upd(u, p);
        return 1;
    }
    int dfs2(int u, int p)
    {
        if(u!=1)upd(p,u);
        //sort(H[u].begin(), H[u].end());
        if (H[u].size())ans[u] = H[u][0].len;
        else
        {
            ans[u] = 0;
            assert(0);
        }
        for (auto &e : G[u])if(e.t!=p)
        {
            int v = e.t;
            dfs2(v,  u);
        }
        return 1;
    }
    int n;
    template<typename INint> inline void IN(INint &x)
    {
        x = 0; int f = 1; char c; cin.get(c);
        while (c<'0' || c>'9') { if (c == '-')f = -1; cin.get(c); }
        while (c >= '0'&&c <= '9') { x = x * 10 + c - '0'; cin.get(c); }
        x *= f;
    }
    #define endl '\n'
    int main()
    {
    #ifndef endl
        freopen("C:\\Users\\VULCAN\\Desktop\\data.in", "r", stdin);
    #endif // !endl
        ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    
        int T(1), cas(0);
        //cin >> T;
        while (cas, T--)
        {
            cin >> n >> k;
            //F(i, 0, n)G[i].clear(), H[i].clear(),in[i]=0;
            F(i, 1, n - 1)
            {
                int f, t; 
                //cin >> f >> t;
                IN(f); IN(t);
                G[f].push_back({ f,t });
                G[t].push_back({ t,f });
            }
            F(i, 1, k) { int p; /*cin >> p;*/IN(p); in[p] = 1; }
            if (k == 0)
            {
                cout << 0 << '\n';
                continue;
            }
            dfs1(1, 0);
            dfs2(1, 0);
            int val = *min_element(ans + 1, ans + 1 + n);
            cout << val << '\n';
        }
        return 0;
    }
    

``

  • 树的直径,简单树形dp
    什么?
    树的直径:树上最远点对之间的距离
    树的直径的性质:
    1. 在一个连通无向无环图中,以任意结点出发所能到达的最远结点,一定是该图直径的端点之一(树也是图)
    2. 距离任意点最远的点一定是直径的一个端点
    3. 树的直径路径上的中点到任意一点的距离都小于等于(直径+1)/2

B.线性基的交

  • 题目:xor

  • 链接:https://ac.nowcoder.com/acm/contest/884/B

  • 大意: 给出1~n(n<5e4)共n个集合,每个集合最多由32个数字组成
    q<=5e4次询问,问数字x是否可有[le,ri]中的任意一个集合中的数字异或得到

  • 分析:知道了之后这就是一个,模板题,线性基可以求并,并之后的线性基的可以得到的数是两个线性基可以一起得到的数。
    线性基还可以求交,交之后的线性基的可以得到的数是两个线性基都可以得到的数

  • 代码

#include<bits/stdc++.h>
#define F(i,a,b) for(int i=(a);i<=ll(b);++i)
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = int(5e4 + 8);

//3_32-线性基
const int maxbit = 35;
struct L_B {
	ll b[maxbit];
	int cnt_lose;
	int cnt;//不rebuild为0,rebuild了才是个数
	L_B()
	{
		memset(b, 0, sizeof(b));
		//memset(rk, 0, sizeof(rk));
		cnt = cnt_lose = 0;
	}
	bool insert(ll val)
	{
		for (int i = maxbit - 1; i >= 0; i--)if (val&((ll)1 << i))
		{
			if (!b[i]) { b[i] = val; break; }
			else val ^= b[i];
		}
		val > 0 ? ++cnt : ++cnt_lose;
		return val > 0;
	}
	bool check(ll val)
	{
		for (int i = maxbit - 1; i >= 0; i--)if (val&((ll)1 << i))
		{
			if (!b[i]) { break; }
			else val ^= b[i];
		}
		val > 0 ? ++cnt : ++cnt_lose;
		return val > 0;
	}

	L_B inter(const L_B &ri)
	{
		L_B &le = *this;
		L_B ALL = le, C, D;
		for (int i = maxbit - 1; i >= 0; --i)if (ri.b[i])
		{
			ll v = ri.b[i], k = ll(1) << i;
			bool ok = 1;
			for (int j = maxbit - 1; j >= 0; --j)if (v&(ll(1) << j))
			{
				if (ALL.b[j]) v ^= ALL.b[j], k ^= D.b[j];
				else
				{
					ok = 0, ALL.b[j] = v, D.b[j] = k;
					break;
				}
			}
			if (ok)
			{
				ll v = 0;
				for (int j = maxbit - 1; j >= 0; --j)if (k&(ll(1) << j))
					v ^= ri.b[j];
				C.insert(v);
			}
		}
		return C;
	}
}lbs;
namespace fastIO {
#define BUF_SIZE 100000
	//fread -> read
	bool IOerror = 0;
	inline char nc() {
		static char buf[BUF_SIZE], *p1 = buf + BUF_SIZE, *pend = buf + BUF_SIZE;
		if (p1 == pend) {
			p1 = buf;
			pend = buf + fread(buf, 1, BUF_SIZE, stdin);
			if (pend == p1) {
				IOerror = 1;
				return -1;
			}
		}
		return *p1++;
	}
	inline bool blank(char ch) {
		return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
	}
	inline void read(ll &x) {
		char ch;
		while (blank(ch = nc()));
		if (IOerror) return;
		for (x = ch - '0'; (ch = nc()) >= '0' && ch <= '9'; x = x * 10 + ch - '0');
	}
#undef BUF_SIZE
};
using namespace fastIO;

vector<ll>G[maxn];
//6_2-线段树
//线段树模板,例子是区间求和+权值求和+最值

#define ls (o<<1)
#define rs (o<<1|1)
#define ndp o,ndl,ndr
#define ndmid ((ndl+ndr)>>1)
#define lsp ls,ndl,ndmid
#define rsp rs,(ndmid+1),ndr
#define ndpf int o,int ndl,int ndr
struct node
{
	L_B lbs;
};
struct set_T
{
	node es[maxn << 2];
	void  comb(node&it, const node&le, const node&ri)//TODO
	{
		it = { le.lbs };
		it.lbs=it.lbs.inter(ri.lbs);
	}
	node getcomb(const node&le, const node&ri)//TODO
	{
		node ret = { le.lbs };
		ret.lbs.inter(ri.lbs);
		return ret;
	}
	void PushUp(int o) { comb(es[o], es[ls], es[rs]); }

	void build(int o, int ndl, int ndr)//TODO:自定义初始化数据
	{
		if (ndl == ndr)
		{ /*es[o].lbs = L_B();*/
			for (auto&v : G[ndl])es[o].lbs.insert(v);
			es[o].lbs.rebuild();
			return;
		}
		build(lsp); build(rsp);
		PushUp(o);
	}
	void prin(ndpf)//TODO:自定义打印信息
	{
		cout << o << ":[" << ndl << "," << ndr << "]:";
		if (ndl == ndr)return;
		prin(lsp); prin(rsp);
	}


	bool query(ll val,int l, int r, ndpf)
	{
		if (l <= ndl && ndr <= r)/*return es[o];*/
		{
			return (es[o].lbs.check(val));
		}
		if (r <= ndmid)return query(val,l, r, lsp);
		else if (l >= ndmid + 1)return query(val,l, r, rsp);
		else return (query(val,l, ndmid, lsp)||query(val,ndmid + 1, r, rsp));
	}
}seg;

template<typename INint> inline void IN(INint &x)
{
	x = 0; int f = 1; char c; cin.get(c);
	while (c<'0' || c>'9') { if (c == '-')f = -1; cin.get(c); }
	while (c >= '0'&&c <= '9') { x = x * 10 + c - '0'; cin.get(c); }
}
int main()
{
	//freopen("C:\\Users\\VULCAN\\Desktop\\data.in", "r", stdin);

	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	//freopen("data.in", "r", stdin);

	int T(1), cas(0);
	while (cas, T--)
	{
		int n, q; cin >> n >> q;
		//cout << clock()*1. / CLOCKS_PER_SEC << endl;
		F(i, 1, n)
		{
			int sz; /*cin >> sz;*/IN(sz);
			F(j, 1, sz)
			{
				ll v;
				 //cin >> v;
				IN(v);
				G[i].push_back(v);
			}
		}
		//cout << clock()*1. / CLOCKS_PER_SEC << endl;
		seg.build(1, 1, n);
		//cout << clock()*1. / CLOCKS_PER_SEC << endl;
		F(i, 1, q)
		{
			int le, ri; ll x;
			//cin >> le >> ri >> x;
			IN(le); IN(ri); IN(x);
			node ret;
			for (int k = 0; k < maxbit; ++k)ret.lbs.b[k] = (ll(1) << k);
			int ans=seg.query(x,le, ri, 1, 1, n);
			
			cout << (ans ? "NO" : "YES") << '\n';
		}
		//cout << clock()*1. / CLOCKS_PER_SEC << endl;
	}
	return 0;
}

C.单调栈+线段树

  • 题目:sequence

  • 链接:https://ac.nowcoder.com/acm/contest/884/C

  • 大意:给定两个长度为n(<=3e6)的序列a,b,求 max ⁡ 1 ≤ l e ≤ r i ≤ n { ( min ⁡ l e ≤ i ≤ r i a i ) ∗ ( ∑ l e ≤ j ≤ r i b j ) } \max \limits_{1\leq le \leq ri \leq n}\{(\min \limits_{le\leq i\leq ri }a_i )*(\sum \limits_{le\leq j\leq ri} b_j) \} 1lerinmax{(leiriminai)(lejribj)}

  • 分析:单调栈枚举作为最小值的a[i],获取le和ri的左边界和右边界,再在边界内选取最大或最小的le和ri使b的和最大或最小

  • 代码

#include<bits/stdc++.h>
#define F(i,a,b) for(int i=(a);i<=ll(b);++i)
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = int(3e6+8);
int a[maxn], b[maxn];
ll L[maxn], R[maxn];
int noL[maxn], noR[maxn];
//#define endl '\n'
template<typename INint> inline void IN(INint &x)
{
	x = 0; int f = 1; char c; cin.get(c);
	while (c<'0' || c>'9') { if (c == '-')f = -1; cin.get(c); }
	while (c >= '0'&&c <= '9') { x = x * 10 + c - '0'; cin.get(c); }
	x *= f;
}
int n;
void init(int n, int h[], int A[], int def)
{
	stack<int>S; S.push(def);
	F(i,1,n)
	{
		while (S.top() != def
			&& h[S.top()]>=h[i])//TODO:大于(<=h[i])还是小于(>=h[i])
			S.pop();
		A[i] = S.top(); S.push(i);
	}
}
void init2(int n, int h[], int A[], int def)
{
	stack<int>S; S.push(def);
	for(int i=n;i;--i)
	{
		while (S.top() != def
			&& h[S.top()] >= h[i])//TODO:大于(<=h[i])还是小于(>=h[i])
			S.pop();
		A[i] = S.top(); S.push(i);
	}
}

#define ls (o<<1)
#define rs (o<<1|1)
#define ndp o,ndl,ndr
#define ndmid ((ndl+ndr)>>1)
#define lsp ls,ndl,ndmid
#define rsp rs,(ndmid+1),ndr
#define ndpf ll o,ll ndl,ll ndr
struct node
{
	ll mi, ma;
};
struct set_T
{
	node es[maxn << 2];
	node comb(const node&le, const node&ri)//TODO
	{
		return { min(le.mi,ri.mi),max(le.ma,ri.ma)};
	}
	void PushUp(ll o) { es[o] = comb(es[ls], es[rs]); }
	void build(ll o, ll ndl, ll ndr,ll ar[])//TODO:自定义初始化数据
	{
		if (ndl == ndr) { es[o] = { ar[ndl],ar[ndl] }; return; }
		build(lsp,ar); build(rsp,ar);
		PushUp(o);
	}
	void prin(ndpf)//TODO:自定义打印信息
	{
		cout << o << ":[" << ndl << "," << ndr << "]:";
		cout << es[o].mi<<' '<<es[o].ma << endl;
		if (ndl == ndr)return;
		prin(lsp); prin(rsp);
	}
	node query(ll l, ll r, ndpf)
	{
		if (l <= ndl && ndr <= r)return es[o];
		if (r <= ndmid)return query(l, r, lsp);
		else if (l >= ndmid + 1)return query(l, r, rsp);
		else return comb(query(l, ndmid, lsp), query(ndmid + 1, r, rsp));
	}
}seg;
namespace IO {
#define BUF_SIZE 100000 
#define OUT_SIZE 100000 
#define ll long long 

	bool IOerror = 0;
	inline char nc() {
		static char buf[BUF_SIZE], *p1 = buf + BUF_SIZE, *pend = buf + BUF_SIZE;
		if (p1 == pend) {
			p1 = buf; pend = buf + fread(buf, 1, BUF_SIZE, stdin);
			if (pend == p1) { IOerror = 1; return -1; }
		}
		return *p1++;
	}
	inline bool blank(char ch) { return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t'; }
	inline bool read(int &x) {
		bool sign = 0; char ch = nc(); x = 0;
		for (; blank(ch); ch = nc());
		if (IOerror) return false;
		if (ch == '-')sign = 1, ch = nc();
		for (; ch >= '0'&&ch <= '9'; ch = nc())x = x * 10 + ch - '0';
		if (sign)x = -x; return true;
	}
	inline bool read(ll &x) {
		bool sign = 0; char ch = nc(); x = 0;
		for (; blank(ch); ch = nc());
		if (IOerror) return false;
		if (ch == '-')sign = 1, ch = nc();
		for (; ch >= '0'&&ch <= '9'; ch = nc())x = x * 10 + ch - '0';
		if (sign)x = -x; return true;
	}
	inline bool read(double &x) {
		bool sign = 0; char ch = nc(); x = 0;
		for (; blank(ch); ch = nc());
		if (IOerror) return false;
		if (ch == '-')sign = 1, ch = nc();
		for (; ch >= '0'&&ch <= '9'; ch = nc())x = x * 10 + ch - '0';
		if (ch == '.') {
			double tmp = 1; ch = nc();
			for (; ch >= '0'&&ch <= '9'; ch = nc())tmp /= 10.0, x += tmp * (ch - '0');
		}
		if (sign)x = -x; return true;
	}
	inline bool read(char *s) {
		char ch = nc();
		for (; blank(ch); ch = nc());
		if (IOerror) return false;
		for (; !blank(ch) && !IOerror; ch = nc())*s++ = ch;
		*s = 0; return true;
	}
	inline void read(char &c) {
		for (c = nc(); blank(c); c = nc());
		if (IOerror) { c = -1; return; }
	}
	//fwrite->write 
	struct Ostream_fwrite {
		char *buf, *p1, *pend;
		Ostream_fwrite() { buf = new char[BUF_SIZE]; p1 = buf; pend = buf + BUF_SIZE; }
		void out(char ch) {
			if (p1 == pend) {
				fwrite(buf, 1, BUF_SIZE, stdout); p1 = buf;
			}
			*p1++ = ch;
		}
		void print(int x) {
			static char s[15], *s1; s1 = s;
			if (!x)*s1++ = '0'; if (x < 0)out('-'), x = -x;
			while (x)*s1++ = x % 10 + '0', x /= 10;
			while (s1-- != s)out(*s1);
		}
		void println(int x) {
			static char s[15], *s1; s1 = s;
			if (!x)*s1++ = '0'; if (x < 0)out('-'), x = -x;
			while (x)*s1++ = x % 10 + '0', x /= 10;
			while (s1-- != s)out(*s1); out('\n');
		}
		void print(ll x) {
			static char s[25], *s1; s1 = s;
			if (!x)*s1++ = '0'; if (x < 0)out('-'), x = -x;
			while (x)*s1++ = x % 10 + '0', x /= 10;
			while (s1-- != s)out(*s1);
		}
		void println(ll x) {
			static char s[25], *s1; s1 = s;
			if (!x)*s1++ = '0'; if (x < 0)out('-'), x = -x;
			while (x)*s1++ = x % 10 + '0', x /= 10;
			while (s1-- != s)out(*s1); out('\n');
		}
		void print(double x, int y) {
			static ll mul[] = { 1,10,100,1000,10000,100000,1000000,10000000,100000000,
				1000000000,10000000000LL,100000000000LL,1000000000000LL,10000000000000LL,
				100000000000000LL,1000000000000000LL,10000000000000000LL,100000000000000000LL };
			if (x < -1e-12)out('-'), x = -x; x *= mul[y];
			ll x1 = (ll)floor(x); if (x - floor(x) >= 0.5)++x1;
			ll x2 = x1 / mul[y], x3 = x1 - x2 * mul[y]; print(x2);
			if (y > 0) { out('.'); for (size_t i = 1; i < y&&x3*mul[i] < mul[y]; out('0'), ++i) {}; print(x3); }
		}
		void println(double x, int y) { print(x, y); out('\n'); }
		void print(char *s) { while (*s)out(*s++); }
		void println(char *s) { while (*s)out(*s++); out('\n'); }
		void flush() { if (p1 != buf) { fwrite(buf, 1, p1 - buf, stdout); p1 = buf; } }
		~Ostream_fwrite() { flush(); }
	}Ostream;
	inline void print(int x) { Ostream.print(x); }
	inline void println(int x) { Ostream.println(x); }
	inline void print(char x) { Ostream.out(x); }
	inline void println(char x) { Ostream.out(x); Ostream.out('\n'); }
	inline void print(ll x) { Ostream.print(x); }
	inline void println(ll x) { Ostream.println(x); }
	inline void print(double x, int y) { Ostream.print(x, y); }
	inline void println(double x, int y) { Ostream.println(x, y); }
	inline void print(char *s) { Ostream.print(s); }
	inline void println(char *s) { Ostream.println(s); }
	inline void println() { Ostream.out('\n'); }
	inline void flush() { Ostream.flush(); }
#undef ll 
#undef OUT_SIZE 
#undef BUF_SIZE 
};
using namespace IO;

int main()
{
	//freopen("C:\\Users\\VULCAN\\Desktop\\data.in", "r", stdin);
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);

	int T(1), cas(0);
	while (cas, T--)
	{
		cin >> n;
		//F(i, 1, n)IN(a[i]); F(i, 1, n)IN(b[i]);
		F(i, 1, n)read(a[i]); F(i, 1, n)read(b[i]);
		F(i, 1, n)L[i] = L[i - 1] + b[i];
		//for (int i = n; i; --i)R[i] = R[i + 1] + b[i];
		init(n, a, noL, 0);
		init2(n, a, noR, n + 1);

		seg.build(1, 0, n, L);
		ll ans = -1ll * inf*inf;
		F(i, 1, n)
		{
			ll val = a[i];
			int le = noL[i] + 1, ri = noR[i] - 1;
			ll tmp;
			if (a[i] > 0)
			{
				//if (i == 3)
					//cout << le << ' ' << ri << endl;

				ll sumL = seg.query(le - 1, i - 1, 1, 0, n).mi;
				sumL = L[i - 1] - sumL;

				ll sumR = seg.query(i, ri, 1, 0, n).ma;
				sumR = sumR - L[i];

				ll sum = sumL + sumR + b[i];
				tmp = val * sum;
				ans = max(ans, tmp);
				//cout << i << ' ' << sumL << ' ' << sumR << ' ' << tmp << endl;
				//cout << endl;
			}
			else
			{
				ll sumL = seg.query(le - 1, i - 1, 1, 0, n).ma;
				sumL = L[i - 1] - sumL;

				ll sumR = seg.query(i, ri, 1, 0, n).mi;
				sumR = sumR - L[i];

				ll sum = sumL + sumR + b[i];
				tmp = val * sum;
				ans = max(ans, tmp);
				//cout << i << ' ' << sumL << ' ' << sumR << ' ' << tmp << endl;
				//cout << endl;
			}
		}
		cout << ans << '\n';

	}
	return 0;
}

D.二进制-中模拟

  • 题目:triples I

  • 链接:https://ac.nowcoder.com/acm/contest/884/D

  • 大意:博士丢给你1e5个1e18范围内的数,让你对每个数n,给博士返回m个数,每个数都要是3的倍数,且这m个数的[或]要等于n

  • 分析:[or]要等于n,那我们返回的数的二进制数中,'1’的bit肯定要是n中有的,并且n中的’1’的bit,我们要全部使用到。要控制每个数都是3的倍数,可以思考一下在二进制数中,怎样才能让一个数是3的倍数:对二进制中的i号位,如果i是偶数,那么它的权(也就是1<<i)模3是1,奇数则是2,这样其实就是在算贡献了。于是我们可以分类了;;;;;;首先假如n%3==0,那就把n返回给博士就可以了,1个数。否则,如果n中既有奇号位,又有偶号位,那就可以拆出那个多余的一个bit,把它和另外一个奇偶性不同的位对应,其他的照样组合(这段不好理解,建议结合代码食用)。如果只有奇号位或者只有偶号位,那就看他们的数量,如果是少于等于2个,是肯定没有方案的(输出-1),否则就跟几个同类拼一伙。

  • 代码

#include<bits/stdc++.h>
#define F(i,a,b) for(int i=(a);i<=ll(b);++i)
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int maxn = int(20);
#define endl '\n'
ll val;
vector<int>pos[3];
ll makenum()
{
	ll ret = 0;
	F(i, 1, 2)
	{
		for (auto &x : pos[i])
		{
			ret |= (1ll << x);
		}
	}
	return ret;
}
vector<ll>Ans;
void prin()
{
	assert((Ans[0] | Ans[1]) == val);
	assert((Ans[0] % 3 == 0));
	assert(Ans[1] % 3 == 0);
	cout << Ans.size();
	for (auto &x : Ans)
	{
		cout << ' ' << x;
	}
	cout << '\n';
	Ans.clear();
}
void solve()
{
	if (val % 3 == 0)
	{
		cout << 1 << ' ' << val << '\n';
		return;
	}
	pos[0].clear();
	pos[1].clear();
	pos[2].clear();
	int p = 0;
	ll tmp = val;
	while (tmp)
	{
		int con;
		if (p & 1)con = 2;
		else con = 1;

		if (tmp & 1)pos[con].push_back(p);
		tmp >>= 1; ++p;
	}

	if (pos[1].size() && pos[2].size())
	{
		int yu = pos[1].size() + pos[2].size() * 2;
		yu %= 3;
		if (yu == 1)
		{
			ll sav = pos[1].back();
			pos[1].pop_back();

			ll v1 = makenum();
			Ans.push_back(v1);

			pos[1].clear();
			while (pos[2].size() != 1)pos[2].pop_back();
			pos[1].push_back(sav);

			ll v2 = makenum();
			Ans.push_back(v2);
			prin();
		}
		else if (yu == 2)
		{
			ll sav = pos[2].back();
			pos[2].pop_back();

			ll v1 = makenum();
			Ans.push_back(v1);

			pos[2].clear();
			while (pos[1].size() != 1)pos[1].pop_back();
			pos[2].push_back(sav);

			ll v2 = makenum();
			Ans.push_back(v2);
			prin();
		}
	}
	else
	{
		vector<int>*it;
		if (pos[1].size())
		{
			//assert(pos[1].size() > 3);
			if(pos[1].size()<3)
			return;
			it = &(pos[1]);
		}
		else 
		{
			//assert(pos[2].size() > 3);
			if(pos[2].size()<3)
				return;
			it = &(pos[2]);
		}

		int yu = (*it).size() % 3;

		vector<int>sav;
		while ((*it).size() % 3 != 0)
		{
			sav.push_back((*it).back());
			(*it).pop_back();
		}

		ll v1 = makenum();
		Ans.push_back(v1);
		for (auto&x : sav)
			(*it).push_back(x);
		reverse((*it).begin(), (*it).end());

		while ((*it).size() != 3)(*it).pop_back();
		ll v2 = makenum();
		Ans.push_back(v2);

		prin();
	}
}


template<typename INint> inline void IN(INint &x)
{
	x = 0; int f = 1; char c; cin.get(c);
	while (c<'0' || c>'9') { if (c == '-')f = -1; cin.get(c); }
	while (c >= '0'&&c <= '9') { x = x * 10 + c - '0'; cin.get(c); }
	x *= f;
}
int main()
{
	//#ifndef endl
		//freopen("C:\\Users\\VULCAN\\Desktop\\data.in", "r", stdin);
	//#endif // !endl
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);

	//F(i, 1,1e6)val=i,solve();
	//for (ll i = (ll)(1e18 - 1e6); i <= ll(1e18); ++i)val = i, solve();
	int T(1), cas(0);
	cin >> T;
	while (cas, T--)
	{
		//cin >> val;
		IN(val);
		solve();
	}
	return 0;
}

B.字符串选手只会PAM,不会SAM,还债了

  • 题目:string

  • 链接:https://ac.nowcoder.com/acm/contest/884/I

  • 大意:在长为2e5的字符串S中,寻找最大的本质不同的子串集合,使对于结合中的任意两个子串a,b.a≠b且a≠rev(b),

  • 分析:辣鸡字符串选手不会SAM::::构造字符串S2=S+(‘z’+1)+rev(S)
    字符串S中的本质不同的子串s分为3类:
    A: s是一个回文串,因此rev(s)是肯定等于s的
    B. s不是一个回文串,但是S中有rev(s)
    C.s不是回文串,且S中没有rev(s)
    将这三类子串的数量暂且叫做nA,nB,nC
    ,那么我们要求的ans其实就使nA+nB/2+nC
    我们对S2跑SAM(要处理一下,不处理超过边界即包含(‘z’+1)的串,其实将las赋值成1就可以了,也可以先直接跑S2,再减去包含(‘z’+1)的本质不同的子串的数量),得到的本质不同的子串的数量(不包含’z’+1)= nA + nB + nC*2 = cnt1
    接下来我们对S跑一遍PAM,可以得到 S中回文串的数量= nA =cnt2
    最后ans = (cnt1+cnt2)/2;

  • 代码

//5_9-后缀自动机-求双串LCS和多串LCS
//本代码用来求多串LCS,可以使用SAM的LCS函数求双串LCS
#include<bits/stdc++.h>
using namespace std;
#define F(i,a,b) for(int i=(a);i<=int(b);++i)
const int maxn = 4e5 + 9;
const int maxm = 28;
struct SAM
{
	struct node
	{
		int len, fa;
		int ch[maxm];
		int pos;
		string str;
	}ns[maxn << 1];
	int las, nid;
	void init()
	{
		las = nid = 1;
		ns[nid] = node();
	}
	void push_back(int c, int p)
	{
		assert(las > 0 && nid > 0);
		int from = las; //p是之前的最长后缀类
		int to = las = ++nid;//np是利用之前的最长后缀类生成的最长后缀类
		ns[to] = node(); ns[to].len = ns[from].len + 1; ns[to].pos = p;
		//ns[to].str = ns[from].str + char('a' + c); cout<<nid<<' ' << ns[to].str << ' ' << ns[to].len << endl;
		for (; from && !ns[from].ch[c]; from = ns[from].fa)ns[from].ch[c] = to;//更新las等价类及其后缀等价类的ch
		int q = ns[from].ch[c];
		if (!from)ns[to].fa = 1;//以上为case 1
		else if (ns[q].len == ns[from].len + 1)ns[to].fa = q;//以上为case 2  找到的第一个las后缀+c的串就是from+c
		else
		{
			ns[++nid] = ns[q]; ns[nid].len = ns[from].len + 1; ns[nid].pos = -1;
			//ns[nid].str = ns[from].str + char('a' + c); cout << ns[nid].str << ' ' << ns[nid].len << endl;
			ns[q].fa = ns[to].fa = nid;
			for (; from&&ns[from].ch[c] == q; from = ns[from].fa)ns[from].ch[c] = nid;//以上为case 3
		}
	}
	//此函数用来求双串LCS
	int LCS(const int str[], int n)
	{
		int v = 1, ans = 0, len = 0;
		F(i, 0, n - 1)
		{
			int c = str[i];
			while (v&&ns[v].ch[c] == 0)v = ns[v].fa, len = ns[v].len;
			if (v == 0)len = 0, v = 1;
			else v = ns[v].ch[c], ++len;
			ans = max(ans, len);
		}
		return ans;
	}
	//ans[maxn<<1]长度为,刚开始初始化为inf,之后再用LCS2跑每个字符串,tmp[1,maxn<<1]暂时保存str[]与自动机的最长子串
	void LCS2(const int str[], int n, int ans[], int tmp[])
	{
		F(i, 0, nid)tmp[i] = 0;
		int v = 1, len = 0;
		for (int i = 0; i < n; ++i)
		{
			int c = str[i];
			while (v&&ns[v].ch[c] == 0)v = ns[v].fa, len = ns[v].len;
			if (v == 0)len = 0, v = 1;
			else ++len, v = ns[v].ch[c];
			tmp[v] = max(tmp[v], len);
		}
		F(i, 2, nid)tmp[i] = max(tmp[ns[i].fa], tmp[i]);
		F(i, 2, nid)ans[i] = min(ans[i], tmp[i]);
	}
	//以fa指针为正向边,在li[1,nid]返回正向拓扑序
	void get_topu(int li[], int in[])
	{
		F(i, 0, nid)in[i] = 0;
		queue<int>Q; int id(0);
		F(i, 2, nid)++in[ns[i].fa];
		F(i, 2, nid)if (!in[i])Q.push(i);
		while (Q.size())
		{
			int u = Q.front(); Q.pop();
			li[++id] = u;
			if (u == 1)break;
			--in[ns[u].fa];
			if (!in[ns[u].fa])
				Q.push(ns[u].fa);
		}
	}
	//以结点的ch指针为正向边,在li[1,nid]中打印正向拓扑序
	void get_topu2(int u, int &id, int in[], int li[])
	{
		F(i, 1, nid)
			F(j, 0, maxm - 1)if (ns[i].ch[j])
			++in[ns[i].ch[j]];
		queue<int>Q;
		F(i, 1, nid)if (!in[i])Q.push(i);
		while (Q.size())
		{
			int u = Q.front(); Q.pop();
			li[++id] = u;
			F(j, 0, maxm - 1)if (ns[u].ch[j])
				if (--in[ns[u].ch[j]] == 0) Q.push(ns[u].ch[j]);
		}
	}
	//使用前用get_topu获取拓扑序
	//li[1,nid-2+1]为拓扑序列(fa树由底向上),inc暂时保存增量,cnt记录结点出现次数
	void count(int li[], int inc[], int cnt[], const int str[], int n)
	{
		F(i, 0, nid)inc[i] = cnt[i] = 0;
		for (int p = 1, i = 0; i < n; ++i)p = ns[p].ch[str[i]], ++inc[p];//初始化inc
		F(i, 1, nid - 2 + 1)
		{
			int u = li[i];
			cnt[u] += inc[u];
			cnt[ns[u].fa] += cnt[u];
		}
	}
	//使用前用get_topu2获取拓扑序
	//li[1,nid]为拓扑序列(ch指针建为反向边),cnt[1,nid]统计每个自动机结点可识别的子串的个数
	void count2(int li[], int inc[], int cnt[], const int str[], int n)
	{
		F(i, 2, nid)inc[i] = 1;
		for (int i = nid; i; --i)
		{
			int u = li[i];
			cnt[u] = inc[u];
			for (int j = 0; j < maxm; ++j)if (ns[u].ch[j])
				cnt[u] += cnt[ns[u].ch[j]];
		}
	}
	//使用前用count2获取ch指针树子树结点数
	string kth_substring(int cnt[], int k)
	{
		string ans;
		int v = 1, sum(0);
		while (sum != k)
		{
			int j = 0;
			while (j < maxm&&sum + cnt[ns[v].ch[j]] < k)sum += cnt[ns[v].ch[j++]];
			if (j == maxm) return "";
			else ans.push_back(char('a' + j)), v = ns[v].ch[j], ++sum;
		}
		return ans + '\n';
	}
	//以fa指针反向为边,建立树(不建立指向父亲的边)
	void create_fa_tree(vector<int>G[])
	{
		F(i, 1, nid)G[i].clear();
		F(i, 2, nid)G[ns[i].fa].push_back(i);
	}
	//在fa反向边的树中dfs,由于上文没有建立指向fa的边,所以遍历不需要判father,这个dfs是在寻找str[]在自动机中的所有endpos(即为下函数服务)
	void dfs_fa_tree(vector<int>G[], int u, vector<int>&endpos)
	{
		if (ns[u].pos != -1)endpos.push_back(ns[u].pos);
		for (auto v : G[u])dfs_fa_tree(G, v, endpos);
	}
	//返回str[]在自动机中出现的endpos位置
	int find(vector<int>G[], const int str[], int n, vector<int>&endpos)
	{
		create_fa_tree(G);//建立fa树
		int v = 1;
		F(i, 0, n - 1)
		{
			int to = ns[v].ch[str[i]];
			if (to == 0)return -1;
			else v = to;
		}
		//cout << ns[v].str << endl;
		dfs_fa_tree(G, v, endpos);//dfs fa树,寻找位置
		return 1;
	}
}sam;
struct PAM {
	struct nd
	{
		//长度,faile指针,最后更新该串的次数,出现次数,回文后缀个数(depth),
		int len, fail, to[maxm];
		//str可显示结点对应的字符串
		string str;
		int pos;
		int inc, cnt, dep;
		//vector<int>G;
	} ns[maxn];

	// s[1,size]为字符串,[0,nid]为结点空间,las为最后插入字符所对应的节点
	int size, nid, las, s[maxn];
	void init()
	{
		size = 0; nid = -1; las = 0;
		ns[++nid] = nd(); ns[nid].len = 0; ns[nid].fail = nid + 1;//0结点 偶回文
		ns[++nid] = nd(); ns[nid].len = -1; ns[nid].fail = nid - 1;//1结点 奇回文
		s[0] = '$' + 1;
	}
	int getfail(int x)  //沿fail找到第一个可插入的回文后缀
	{
		while (s[size] != s[size - ns[x].len - 1]) x = ns[x].fail;
		return x;
	}
	void push_back(int c)
	{
		assert(ns[0].len == 0 && ns[1].len == -1);
		s[++size] = c;
		las = getfail(las);  //找到插入的位置
		if (!ns[las].to[c])  //若没有这个节点,则新建并求出它的fail指针
		{
			ns[++nid] = nd();
			ns[nid].len = ns[las].len + 2;
			ns[nid].fail = ns[getfail(ns[las].fail)].to[c];
			ns[nid].dep = ns[las].dep + 1;
			//ns[ns[nid].fail].G.push_back(nid);//维护字符串拓展树
			ns[las].to[c] = nid;
			//debug用,显示每个结点对应的字符串
			//if (ns[nid].len == 1)ns[nid].str.push_back(char('a' + c));
			//else ns[nid].str = char('a' + c) + ns[las].str + char('a' + c);
			//cout << nid << ':' << ns[nid].str << '\n';
		}
		las = ns[las].to[c];
		ns[las].inc++;
	}
	void count()//重新计算每个回文串出现的次数
	{
		for (int i = nid; i >= 0; --i)ns[i].cnt = 0;
		for (int i = nid; i > 1; --i)
			ns[i].cnt += ns[i].inc, ns[ns[i].fail].cnt += ns[i].cnt;
	}
}pam;
string A, B;
int main()
{
	//freopen("C:\\Users\\VULCAN\\Desktop\\data.in", "r", stdin);
	int T(1);
	//cin >> T;
	while (T--)
	{
		sam.init(); pam.init();
		cin >> A;
		for (auto c : A)pam.push_back(c - 'a');
		int cnt_huiwen = (pam.nid - 2) + 1;
		B = A; reverse(B.begin(), B.end());
		long long ans(0);
		if (0)
		{
			A += char('z' + 1); A += B;
			F(i, 0, A.size() - 1)sam.push_back(A[i] - 'a', i)/*, cout << i << ' ' << sam.ns[4].fa << endl*/;
			F(i, 2, sam.nid)ans += sam.ns[i].len - sam.ns[sam.ns[i].fa].len/*, cout << ans << endl*/;
			long long  n = B.length();
			ans -= (n + 1)*(n + 1);
		}
		if (1)
		{
			F(i, 0, A.size() - 1)sam.push_back(A[i] - 'a', i);
			sam.las = 1;
			F(i, 0, A.size() - 1)sam.push_back(B[i] - 'a', i + A.size());
			F(i, 2, sam.nid)ans += sam.ns[i].len - sam.ns[sam.ns[i].fa].len/*, cout << ans << endl*/;
		}
		//cout << ans << '\n';
		//cout << pam.nid - 2 + 1 << '\n';
		ans += (pam.nid - 2) + 1;
		assert(ans % 2 == 0);
		ans /= 2;
		cout << ans << '\n';
	}
	return 0;
}

//What to Debug
/*
-1.最好把全部warning都X掉,否则:https://vjudge.net/solution/19887176
0.看看自己是否有可能需要快读,禁endl
1.数组越界,爆int,浮点精度(查看精度是否达到题目要求,看有没有浮点数比较:eps),取模操作,初始化数组,边缘数据,输出格式(cas),强制在线是否更新了las
2.通读代码,代码无逻辑错误
3.读题,找到题意理解失误或算法错误
4.放弃
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值