1. 程式人生 > 實用技巧 >2020.11.25清北學堂訓練賽解題報告

2020.11.25清北學堂訓練賽解題報告

難易程度

中等偏上,該會的題都拿到分了,依舊是被學長虐菜的一天

題目分析

T1 一

題目分析
  • 擷取問題:使用substr擷取子串(位置,長度)
  • 編號:用map當作容器,下標為字串型別,裡面存的為整數型別的,也就是這個變數的編號,對映到map上,輕鬆解決麻煩的變數判斷問題
  • 判斷:手玩一下\(+=\)符號的出現次數可以看到
    • 當為\(0\)時,輸出變數的值
    • 當為\(1\)時,定義變數
    • 當為\(2\)時,對變數進行修改
  • 最後一下注意幾個修改時的問題
    • 兩個整數為相加
    • 兩個字串為相連線
    • 被修改值為整數,修改值為字串則不進行處理
    • 被需改值為字串,修改值為整數型別,將整數型別轉換成字元型別連線上去
程式碼實現
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<stack>
#include<algorithm>
#include<cmath>
#include<map> 
using namespace std;
const int N=1e5+9;
int vis[N],num[N];//訪問標記,儲存數字的 
string car[N];//儲存字元的 
int pd_num[N],pd_car[N];//判斷變數是字元還是數字 
string s;//輸入的字串 
int cnt;//標號 
map<string,int> mp;//新增一個新標號 
int n;
int main()
{
	cin>>n;
	while(n--)
	{
		cin>>s;
		int flag=0,chung=0;//總標記,擷取標記 
		int pd1=0,pd2=0;
		for(int i=0;i<s.length();i++)
		{
			if(s[i]=='='||s[i]=='+')
			{
				if(flag==0)
				chung=i;
				flag++;
			}
			if(s[i]=='"')//字元 
			{
				if(!pd1)
				pd1=i;
				else pd2=i;
			}
		}
		if(flag==0)
		{
			string sum=s.substr(0,s.length());
			if(!vis[mp[sum]])
			cout<<"no"<<endl;
			else 
			{
				if(pd_num[mp[sum]])
				cout<<num[mp[sum]]<<endl;
				else cout<<car[mp[sum]]<<endl;
			}
		}
		if(flag==1)
		{
			string sum=s.substr(0,chung);//擷取變數名字 
			mp[sum]=++cnt;//給一個標號
			vis[mp[sum]]=1;
			if(!pd1)//這是一個數字 
			{
				pd_num[mp[sum]]=1;
				int he=0;
				for(int i=chung+1;i<s.length();i++)
				{
					
					int mat=int(s[i])-48;
					he*=10;
					he+=mat; 
				} 
				num[mp[sum]]=he;
			}
			else
			{
				pd_car[mp[sum]]=1;
				for(int i=pd1+1;i<=pd2-1;i++)
				{
					car[mp[sum]]+=s[i];
				}
			}
		}
		if(flag==2)//加法操作 
		{
			string sum=s.substr(0,chung);
			if(!vis[mp[sum]]) continue;
			if(pd_num[mp[sum]]&&pd1) continue;
			if(!pd1&&pd_num[mp[sum]])
			//如果兩者均為整數直接相加;
			{
				int he=0;
				for(int i=chung+2;i<s.length();i++)
				{
					
					int mat=int(s[i])-48;
					he*=10;
					he+=mat; 
				} 
				num[mp[sum]]+=he;
			}
			if(pd1&&pd_car[mp[sum]])
			//如果兩者均為字串直接進行字串拼接。
			{
				for(int i=pd1+1;i<=pd2-1;i++)
				{
					car[mp[sum]]+=s[i];
				}
			}
			if(!pd1&&pd_car[mp[sum]])
			//如果x是字串y是整數則將y轉換為字串進行字串拼接 
			{
				for(int i=chung+2;i<s.length();i++)
				{
					car[mp[sum]]+=s[i];
				}
			}
		}
	} 
}

T2 二

題目分析

爆搜可以過!!!

  • 給定好幾個長度相等的字串,要求找出滿足下列條件的字串個數
    • 可以任意重定義 \(26\) 個字母的優先順序,並且任意
      排列字串
    • 其他字串經過任意排列以後的字典序都大於該字串
  • 我們可以發現,每一個字串的原來排列的順序是沒有什麼貢獻的,我們可以先記錄一下每一個字串每個字元的出現次數,設 \(cnt_{i,x}\) 表示第 \(i\) 個字串的字元 \(x\) 出現的次數
  • 因為資料範圍不大,所以我們考慮用暴力列舉合法的字串,在暴力列舉其他的字串檢查。
思路實現

假設當前的字串編號為 \(i\)

考慮是否存在一種字典序,是的這種字串的字典序最小。

字典序先由小到大,考慮這 \(26\)

個字元的字典序分配情況,考慮當前要分配到的最小字元,對於某種字元,顯然應該滿足 \(cnt_{i,x}\geq cnt _{j,x}\),他才能成為最小的字符合串

注意的是此處的\(j\)表示填到這一位時,字典序仍有可能比 \(i\) 小的字串 \(j\)

每次填入一個字元 \(x\)都會削除一些滿足\(cnt_{i,x}>cnt_{j,x}\)的字串

我們可以發現這一個位置能填的字元可能有很多,但是舉幾組樣例就可以發現,填入不同的字元效果是相通的,他們填完之後可以削除的字串\(j\)的數量相同,於是隨便填就可以了

時間複雜度為\(O(26^2n^2)\)

實現的時候只需要填入字串\(i\)中的字元即可

程式碼實現

(待新增......)

T3 三

題目簡述

給定一個長度為\(n\)的序列\(a\) ,給定一個引數為\(k\)。設一共有\(M\)個區間\(p\),第\(i\)個區間為\(p_i=[l_i]\)滿足其中的逆序對數不小於\(k\),設\(f(p_i,p_j)\)表示區間\([l_i,r_i]\)\([l_j,r_j]\)重複覆蓋的位置數量。求:

\[\sum ^{m}_{i=1} \sum^{m}_{j=i+1} f(p_i,p_j) \]

題目分析
  • 把題目拆分成兩部分,考慮如何求\(m\)這個區間
  • 先考慮暴力列舉的方式,如果當前列舉的區間為\([l,r]\),發現只要區間擴發,逆序對數是不降的,區間縮小時,逆序對數是不增的,具有單調性,這可以看成一個類似於滑動視窗的問題,考慮用雙指標來維護
  • 考慮列舉一下區間的左端點\(l\),設第一個滿足逆序對數\(\geq k\)的右端點為\(r\),顯然不小於\(r\)的位置成為這個區間的右端點也是合法的。
  • 繼續考慮區間的左端點\(l\)右移,那麼\(r\)也一定會單調右移,考慮如何削除\(l\)對逆序對的影響,再加入新數的影響。
  • 權值樹狀陣列來維護列舉的區間的值域
  • 維護的時間複雜度為\(O(log n)\)
思路拓展

我們考慮一下把式子化簡一下

\[\sum^{m}_{i=1} \sum^{m}_{j=i+1} f(p_i,p_j)=\sum^{m}_{i=1} \sum^{m}_{j=i+1} \sum_{k\in p_i \cap p_j} 1= \sum^n_{i=1} \sum^{m}_{k\in p_i} \sum^{m}_{k \in p_j} 1 \]

在考慮一下實際的意義,後面兩個\(\sum\)表示的是從覆蓋的位置\(k\)的區間裡找出兩個區間來,所以上面的式子可以化為

\[\sum^{n}_{i=1} (\frac{cnt_k}{2}) \]

其中的\(cnt_k\)表示\(k\)被多少個區間覆蓋

在考慮求\(m\)個區間的過程中維護\(cnt\),如果當前列舉的區間為\([l,r]\),其對\(1~r\)的貢獻為\(n-r+1\),對於\(r+1\)的貢獻為\(n-r\),對於\(r+2\)的貢獻為\(n-r-1\),對\(n\)的貢獻為\(1\)

這就是一個區間加等差數列,二階查分維護即可。

求得\(cnt\)後列舉位置\(k\)計算和式即可,時間複雜度為\(O(n \log n)\)

T4 四

建議去世…………

賽後總結

正常的知識點考察和程式碼能力基本沒有問題,現在知識點已經不是關鍵,主要是講究的思維能力,在空餘時間做一些有思維難度的題目,鍛鍊一下自己的技巧和思維方式

知識點彙總

T1 模擬,字串
T2 字串, 字典序
T3 數論,單調佇列
T4 樹形dp