# 2021. Supplementary questions for the tenth session of Niuke multi school in summer vacation

Posted by Denness on Tue, 21 Sep 2021 04:37:28 +0200

1. Browser Games String hash / dictionary tree compression

Given n strings, the length of each string shall not exceed 100. For each string, its information can be queried through a prefix. And make sure that any string is not a prefix to another string. Define ans[i] as the minimum number of prefix strings that can query the information of the first I strings and ensure that the information of the following strings cannot be queried. Finally, ans[i] is output in turn. (the space limit of this question is very strict, so you can only O ( n ) O(n) O(n))

Idea 1: String hashing. For directly querying n strings, we can count the number. Let each prefix be the first character of the corresponding string, and then de duplication is the minimum number. We consider moving forward from the back. When removing the nth string, we can traverse the prefix of the string through string hashing. If there is a difference with us If there is a conflict between the query prefixes processed, it is obviously not acceptable, because the information of string n will be found. We only need to add one more character to those conflicting query prefixes.

The specific codes are as follows:

#include <bits/stdc++.h>
using namespace std;
const int N=100001,bas=131; // If N is written as 1000010, the memory will explode
typedef unsigned long long ULL;
unordered_map<ULL,vector<int> >mp;
ULL p[N];
string s[N];
int n,pos[N],ans[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>s[i];
p[i]=s[i][0]; // Save string hash value
pos[i]=0; //Record where the query prefix of each string has been added
mp[p[i]].push_back(i);
}
for(int i=n;i>=1;i--)
{
ans[i]=mp.size();
ULL pre=0;
for(auto &c:s[i]) //Traverse the prefix of the current string to see if it conflicts with the query prefix
{
pre=pre*bas+c;
auto it=mp.find(pre);
if(it==mp.end())continue; //There is a conflict
for(auto &id:it->second)
{
if(id==i)continue;
++pos[id];
p[id]=p[id]*bas+s[id][pos[id]];
mp[p[id]].push_back(id);
}
mp.erase(it);
}
}
for(int i=1;i<=n;i++)cout<<ans[i]<<"\n";
return 0;
}


Method 2: shrink the dictionary tree

When we see the prefix related problems, we subconsciously think of dictionary tree writing. If we don't consider the space constraints, we can first build all strings into a dictionary tree. Because no string is the prefix of another string, it is obvious that we can directly use each string as a query prefix. However, we should minimize the number of prefixes and And it cannot be the prefix of a string not added to the set. Therefore, in the dictionary tree, the node representing the end address of the query prefix should be as close as possible, and the more the prefix is, the more likely it is to be the prefix of multiple strings. Therefore, for each time, we can query the prefix. We enumerate i and add it to the set while calculating. For traversing to string i, we start from the end of the string The corresponding leaf node in the dictionary tree should go up as much as possible. For a bifurcation point, if the number of times the point goes = the number of sons of the node, it means that the prefix will not query the points that are not added to the set, and you can continue to go up, otherwise you can't. the next problem is to establish the dictionary tree. Because we want to go from bottom to top, we need to make each node point to For its parent node, we can build a tree recursively. The next step is to save memory and shrink points. For nodes with only one branch in the future, we can replace them with one point. For a series of points with the same prefix, we can compress them into one point. These can be implemented recursively. Compare and test the code ability. See the code Notes for details:

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+7;
struct node{
int del,son,fa;
}tr[N*100];
int idx;
int n;
char str[N][103];
int strid[N];
int stred[N];
int getid(char c)
{
if(c>='a'&&c<='z')return c-'a';
if(c>='A'&&c<='Z')return c-'A'+26;
if(c>='0'&&c<='9')return c-'0'+52;
if(c=='.')return 62;
return 63;
}
int dfs(int dep,int l,int r) //Recursive tree building
{
if(l==r)//When you have only one son, you don't need recursion to reduce memory
{
int u=++idx;
stred[strid[l]]=u;
return u;
}
bool f=1;
for(int i=l;i+1<=r;i++)
{
if(str[strid[i]][dep]!=str[strid[i+1]][dep])
{
f=0;
break;
}
}
if(f)//Those with the same prefix are compressed into one point
{
int u=dfs(dep+1,l,r);
if(dep)return u; //If the same prefix exists and is compressed into a point, the root node returned by the recursive function we write does not actually exist, just to build a tree
tr[u].fa=0;  //However, the compressed point here needs to return the compressed point. If the beginning is the same character, we directly return u, then we regard u as a virtual node, but in fact this point
return 0;  // It can be taken, so if dep=0, we need to build a virtual node
}
vector<int> vt[64];
for(int i=l;i<=r;i++) //In the dictionary tree, the same prefix will share nodes, so we put the same characters in the bucket for grouping and recursion,
vt[getid(str[strid[i]][dep])].push_back(strid[i]);

int cnt=l;
int u=++idx;
for(auto v:vt)
{
if(v.size()==0)continue;
for(auto j:v)strid[cnt++]=j;// Renumber
tr[dfs(dep+1,l,l+v.size()-1)].fa=u;
tr[u].son++;
l+=v.size();

}
return u;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
for(int i=0;i<n;i++)
{
cin>>str[i];
strid[i]=i;//Save the number of the string. The following operations can be completed directly with the number
}
int rt=dfs(0,0,n-1);
int ans=0;
for(int i=0;i<n;i++)
{
ans++;
int nw=stred[i];
while(tr[nw].fa!=rt)
{
nw=tr[nw].fa;
tr[nw].del++;
if(tr[nw].del==tr[nw].son)ans-=tr[nw].son-1;
else break;
}
cout<<ans<<"\n";
}
return 0;
}


Summary: master the essence of the dictionary tree, and then use different tree building methods to master the method of reducing the point of the dictionary tree

1. Train Wreck Thinking, stack, greed

General idea: give a string containing '(' and ')' to ensure that there are n trains with numbers 1-n in pairs, '(' represents the train entering the stack and ')' represents the train leaving the station. First give the color sequence a representing the train number, a i a_i ai represents the color of the train numbered i. now it is necessary to adjust the color sequence so that the color sequence represented by the train is different when a train enters the stack.

For example () (()) () there are 4 times to enter the stack, and the train numbers represented are [1], [2], [2,3] and [4].

Idea: first of all, the stack operation should be replaced by dfs. The out of stack operation corresponds to the backtracking in dfs. Because we directly consider the train number corresponding to each time of entering the stack, and the stack is one-time, so we consider building a map with dfs and using 0 as the root node. After building the map, the problem is transformed into a root node (not counting the root node) The color sequence represented by the chain to any node is different. For any node u, it has several child nodes. To ensure that the color sequence from the root node to its child node is different, only when the color of its child node is different. Therefore, we further transform the problem. When we assign color, we only need to make all the child nodes of each node The color of the child node is different. For allocation, we can use set greedy to allocate.

The code is as follows:

#include <bits/stdc++.h>
using namespace std;
const int N=1000010;
typedef pair<int,int> PII;
char s[N<<1];
vector<int> g[N];
int n,cnt[N],ans[N],stk[N],top,id;
priority_queue<PII> q;
PII p[N];
bool f=1;
void dfs(int u)
{
if(f)
{
int t=0;
for(auto v:g[u])
{
if(q.size()>0)
{
auto tmp=q.top();
q.pop();
ans[v]=tmp.second;
if(tmp.first>1)p[t++]={tmp.first-1,tmp.second};
}
else {f=0;break;}
}
for(int i=0;i<t;i++) q.push(p[i]);
for(auto v:g[u])dfs(v);
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>(s+1);
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
cnt[x]++;
}
for(int i=1;i<=n;i++)if(cnt[i])q.push({cnt[i],i});
for(int i=1;i<=(n<<1);i++)
{
if(s[i]=='(')
{
g[stk[top]].push_back(++id);
stk[++top]=id;
}
else top--;
}
dfs(0);
if(f)
{
cout<<"YES"<<"\n";
for(int i=1;i<=n;i++)cout<<ans[i]<<" ";
}
else cout<<"NO"<<"\n";
return 0;
}


Conclusion: the problem of stack can be transformed into a graph theory problem.

1. War of Inazuma (Easy Version) It's reading comprehension again. I can't understand the topic

Given have 2 n 2^n 2n points 0 − 2 n − 1 0-2^n-1 0 − 2n − 1. It is specified that two points are adjacent if and only if the binary bits of the two points are different. First divide them into two camps, represented by 0 and 1. It is required that no more points in the same camp in any point and its adjacent points n \sqrt{n} n Up the whole. Output any legal.

Idea: suppose A and B are adjacent, the binary representation of A has x 1, and the binary representation of B has Y 1, x!=y, because once x=y, if the position of 1 is not one-to-one corresponding, more than one binary bit is different. Once one-to-one corresponding, there must be no way to find A different binary bit. So x!=y. you might as well make x < y. because only one binary bit is different, y=x+1, both The parity is different. So we can divide the camp according to the parity of the number of binary bits 1.

The code is as follows:

#include <bits/stdc++.h>
using namespace std;
int n;
int cale(int x)
{
int cnt=0;
while(x)
{
x-=(x&-x);
cnt++;
}
return cnt;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
for(int i=0;i<(1<<n);i++)
{
if(cale(i)%2)cout<<0;
else cout<<1;
}
return 0;
}


Conclusion: there are too many people's questions. If you really can't think of it, you can guess it boldly.