星期一:
再学kmp,学的最明白的一次
贴道kmp的题 洛谷传送门
思路:答案为n-ne【n】,把字符串画两遍理解一下
思路:最长周期,复制一遍过后要求覆盖原字符串,及字符串中非周期的后缀与周期的部分前缀相等,因为周期要最长,所以后缀要最短,即求大于0的情况下最短相等前后缀,依然能用next数组求,不断j=ne【j】即可,记得压缩路径,不然会 T
代码如下:
const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
int ne[N];
void solve(){string s; cin >> n >> s; s=" "+s;ne[1]=0;for(int i=2,j=0;i<=n;i++){while(j && s[i]!=s[j+1]) j=ne[j];if(s[i]==s[j+1]) j++;ne[i]=j;}ll ans=0;for(int i=1;i<=n;i++){if(!ne[i]) continue;int j=ne[i];while(ne[j]) j=ne[j];ne[i]=j; //路径压缩ans+=i-j;}cout << ans;
}
星期二:
历时两天,终于拿下这题 atc传送门
思路:kmp加上状压板子,不难,但做这题的时间跨度很长
sa【i】【j】表示字符串 j 接在 i 后面实际增加的长度,用kmp预处理出来
wa了很多发是因为去掉被包含的字符串时,一边遍历vector一边erase,后来加了个临时储存的vector,一个个添加到ves里就过了,教训是谨慎使用erase,特别是遍历时
代码如下:
const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
vector<string>ves;
int ne[N];
int sa[22][22];
ll dp[1<<21][22];
bool check(string s1,string s2){int m=s1.size(),n=s2.size();s1=" "+s1,s2=" "+s2;ne[1]=0;for(int i=2,j=0;i<=n;i++){while(j && s2[i]!=s2[j+1]) j=ne[j];if(s2[i]==s2[j+1]) j++;ne[i]=j;}for(int i=1,j=0;i<=m;i++){while(j && s1[i]!=s2[j+1]) j=ne[j];if(s1[i]==s2[j+1]) j++;if(j==n) return 1;}return 0;
}
int ask(string s1,string s2){string s=" "+s2+s1;int n=s.size()-1;ne[1]=0;for(int i=2,j=0;i<=n;i++){while(j && s[i]!=s[j+1]) j=ne[j];if(s[i]==s[j+1]) j++;ne[i]=j;}return ne[n];
}
void solve(){cin >> n;vector<string>tmp;for(int i=1;i<=n;i++){string s; cin >> s;tmp.push_back(s);}sort(tmp.begin(),tmp.end());tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end());for(int i=0,sz=tmp.size();i<sz;i++){bool if1=1;for(int j=0;j<sz;j++){if(i==j) continue;if(check(tmp[j],tmp[i])) if1=0;}if(if1) ves.push_back(tmp[i]);}n=ves.size();for(int i=0;i<n;i++){for(int j=0;j<n;j++){if(i==j) continue;sa[i][j]=ves[j].size()-ask(ves[i],ves[j]);}}for(int mask=0;mask<=(1<<n);mask++){for(int i=0;i<=n;i++) dp[mask][i]=1e18;}for(int i=0;i<n;i++) dp[1<<i][i]=ves[i].size();for(int mask=0;mask<(1<<n);mask++){for(int i=0;i<n;i++){if(!(mask&1<<i)) continue;for(int j=0;j<n;j++){if(mask&1<<j) continue;int nmask=mask|(1<<j);dp[nmask][j]=min(dp[mask][i]+sa[i][j],dp[nmask][j]);}}}ll ans=1e18;for(int i=0;i<n;i++) ans=min(dp[(1<<n)-1][i],ans);cout << ans;
}
下午蓝桥模拟赛,打的还行
一道全排列暴力的题,没有思路,贴上是因为对于飞机降落的时间判断失误,痛失60分,贴着给自己长个记性
思路:区间dp,遇到两次都是用暴力冲过去的
dp【i】【j】表示区间 i,j 是否可翻转
转移:若s【i】> s【j】,dp【i】【j】=1,否则若s【i】== s【j】,dp【i】【j】=dp【i+1】【j-1】
代码如下:
ll n;
string s;
ll dp[5050][5050];
void solve(){cin >> s;n=s.size();s=" "+s;for(int i=1;i<n;i++)if(s[i]>s[i+1]) dp[i][i+1]=1;for(int len=3;len<=n;len++){for(int l=1;l+len-1<=n;l++){int r=l+len-1;if(s[l]>s[r]) dp[l][r]=1;else if(s[l]==s[r]) dp[l][r]=dp[l+1][r-1];}}ll ans=0;for(int i=1;i<=n;i++){for(int j=i+1;j<=n;j++)ans+=dp[i][j];}cout << ans;
}
星期三:
背包dp第二题: cf传送门
思路:dp【i】【j】表示考虑到第 i 个,放了 j 个数的最大价值
转移:dp【i】【j】从dp【i】【j-1】或dp【i-1】【j】转移过来
代码如下:
const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
ll p,q,r;
int a[N];
ll dp[N][4];
void solve(){cin >> n >> p >> q >> r;for(int i=1;i<=n;i++){cin >> a[i];dp[i][1]=dp[i][2]=dp[i][3]=-1e18;}dp[1][1]=p*a[1];dp[1][2]=(p+q)*a[1];dp[1][3]=(p+q+r)*a[1];for(int i=2;i<=n;i++){dp[i][1]=max(dp[i-1][1],p*a[i]);dp[i][2]=max(dp[i][1]+q*a[i],dp[i-1][2]);dp[i][3]=max(dp[i][2]+r*a[i],dp[i-1][3]);}cout << dp[n][3];
}
一天vp了两场cf global round,越vp越对6号晚上没信心,上午那场卡A,晚上这场卡B
A就不说了,纯属我脑子不好使,贴下第二场的B cf传送门
思路:答案为最大值 除以 所有数的gcd,有点赛时猜结论的风格
代码如下:
const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
int a[N],gc;
int gcd(int a,int b){return b==0?a:gcd(b,a%b);
}
void solve(){cin >> n;gc=0;for(int i=1;i<=n;i++){cin >> a[i];gc=gcd(a[i],gc);}cout << a[n]/gc << "\n";
}
就酱紫,好好休息,清明节假期猛猛练
星期四:
cf global round 23,B题越看越不想看,跳了,补C cf传送门
思路:比较简单,因为操作很强,逆序对必能都填上
贴这题是因为使用vector时又忘了判是否为空了
代码如下:
const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
int p[N];
void solve(){cin >> n;for(int i=1;i<=n;i++) cin >> p[i];vector<PII>ve;for(int i=1;i<n;i++)if(p[i]>p[i+1])ve.push_back({p[i]-p[i+1],i+1});sort(ve.begin(),ve.end(),greater<PII>());if(ve.empty()){for(int i=1;i<=n;i++) cout << "1 ";cout << "\n";return ;}for(int i=1;i<=n;i++){if(ve.size() && i>=ve.back().first) cout << ve.back().second << " ",ve.pop_back();else cout << 1 << " ";}cout << "\n";
}
接着补D cf传送门
思路:树上dp,贪心的想每条路径一定会从根节点到子节点
假设父节点有c条路径,sz个子节点,根据题意,每个子节点分配的路径数为c/sz(向下取整 或 c/sz(向上取整,算出子节点分配路径为向上和向下取整后的值,根据其差值排序,前c %sz个子节点向上取整
代码如下:
const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
int s[N],k;
vector<int>ve[N];
ll dp[N][2];
void dfs(int x,int c){ //节点为x,有c条路径在此节点dp[x][0]=1ll*s[x]*c;dp[x][1]=1ll*s[x]*(c+1);if(ve[x].empty()) return ;int sz=ve[x].size();vector<int>tmp;for(auto i:ve[x])dfs(i,c/sz),tmp.push_back(i);for(auto i:tmp) dp[i][1]-=dp[i][0];sort(tmp.begin(),tmp.end(),[&](int a,int b){return dp[a][1]>dp[b][1];});int lef=c%sz;for(int i=0,tsz=tmp.size();i<tsz;i++){dp[x][0]+=dp[tmp[i]][0];if(i<lef) dp[x][0]+=dp[tmp[i]][1];dp[x][1]+=dp[tmp[i]][0];if(i<=lef) dp[x][1]+=dp[tmp[i]][1]; //父节点向上取整也可以给子节点多分配一个}
}
void solve(){cin >> n >> k;for(int i=1;i<=n;i++) ve[i].clear();for(int i=2;i<=n;i++){int p; cin >> p;ve[p].push_back(i);}for(int i=1;i<=n;i++) cin >> s[i];dfs(1,k);cout << dp[1][0] << "\n";
}
cf global round 24 C cf传送门
思路:把数分为两个集合,一个集合为大数,另一个存小数,两集合间任意建边,边数为sum1 * sum2,遍历一遍大数小数的界限,记录最大答案即可
代码如下:
const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
int a[N];
void solve(){cin >> n;for(int i=1;i<=n;i++) cin >> a[i];sort(a+1,a+n+1);if(a[1]==a[n]){cout << n/2 << "\n";return ;}ll ans=0;for(int i=1;i<=n;i++){while(a[i]==a[i+1]) i++;ll sum1=i,sum2=n-i;ans=max(sum1*sum2,ans);}cout << ans << "\n";
}
星期五:
下午蓝桥训练赛,被dp拿下了,准备补题
晚上牛客小白月赛,做到了E, STL题,还不错
dp题单数位dp第三题: cf传送门
思路:假设先对n作一步处理, 结果为1000以内的数,把1000以内的数到1的操作数预处理出来
然后开始填数,保证其小于等于n,填好后即为未操作过的数,1的数量cnt1即为操作一步后的数,若cnt1到1的操作数为k-1,即符合条件
特判,k为0,ans为1,k为1时,判定条件为cnt【cnt1】==0,1也会被算进答案里,故答案减1
代码如下:
ll n;
string s;
int k;
int num[1010],cnt[1010];
ll dp[1010][1010][2];
int getc(int x){int res=0;while(x!=1) x=__builtin_popcount(x),res++;return res;
}
int dfs(int lef,int cnt1,int if1){ //还剩lef长度没填,目前填了cnt1个1,是否为上界状态if(!lef){if(!cnt1) return 0;return cnt[cnt1]==k-1; //一步操作为cnt1,再k-1步操作为1,则合法}if(dp[lef][cnt1][if1]!=-1) //记忆化处理return dp[lef][cnt1][if1];ll res=0;if(if1){ //为上界状态if(num[lef]){ res+=dfs(lef-1,cnt1+1,1),res%=mod; //填1,保持上界状态res+=dfs(lef-1,cnt1,0),res%=mod;}else res+=dfs(lef-1,cnt1,1),res%=mod; //这位为0,就只能填0}else{ //否则填0或1均可res+=dfs(lef-1,cnt1+1,0),res%=mod;res+=dfs(lef-1,cnt1,0),res%=mod;}return dp[lef][cnt1][if1]=res; //记忆化处理
}
void solve(){cin >> s >> k;if(!k){cout << 1; return ;}for(int i=1;i<=1000;i++) //预处理操作数cnt[i]=getc(i);int sz=s.size();for(int i=1;i<=sz;i++) num[i]=s[sz-i]-'0';memset(dp,-1,sizeof dp);ll ans=dfs(sz,0,1);if(k==1) ans--;cout << ans;
}
星期六:
蓝桥杯补题:
思路:前缀和预处理
(a【j】- a【i-1】)%k==0,可以转化为a【j】%k==a【i-1】%k
代码如下:
ll n;
int sum,k;
map<int,int>mp;
void solve(){cin >> n >> k;ll ans=0;mp[0]=1;for(int i=1;i<=n;i++){int x; cin >> x;sum+=x,sum%=k;ans+=mp[sum];mp[sum]++;}cout << ans;
}
晚上abc和cf global round25,都止步于D
星期天:
思路:用前缀异或和能优化到n^2,继续优化则考虑拆位
Sl-1 ^ Sr从二进制位上看,两数在 i 位上相异,就能对答案产生1<< i 的贡献,任意的一个0和一个1就能产生这样的区间,所以按位统计前缀和中的0和1的数量,能在 i 位数上产生sum0*sum1个区间,对答案产生 1<< i * sum0*sum1 的贡献
这里还有一个注意的点,在统计前缀和第 i 位0 1数量时,我使 a【j】 & 1<< i ,这样是错误的,因为如果a【j】第 i 位为0,上式结果还是0,但若为1,上式结果会是1<< i ,即产生了越界,并没有被统计到1的数量里,在布尔式判断对错时,a【j】& 1<< i 和 a【j】>> i & 1 效果是相同的,但这种情况就并非如此了,需注意
代码如下:
const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
int a[N];
int c[22][2];
void solve(){cin >> n;for(int i=1;i<=n;i++){cin >> a[i];a[i]^=a[i-1];}for(int i=0;i<=20;i++)for(int j=0;j<=n;j++)c[i][a[j]>>i&1]++; //a[j]&1<<i结果为1<<i而并非1ll ans=0;for(int i=0;i<=20;i++)ans+=(1ll<<i)*c[i][0]*c[i][1];cout << ans;
}