杭电第一场补题 1.2.3.5.9
杭电第一场补题
1009:ASSERTION
题目:
询问:m个物品分到n个组里面,是否一定有一个组的物品>=d。
思路:
如果 \(m>n*(d-1)\) 就是对的。
1002.City Upgrading
题目:
给定一棵树,问最小权值点覆盖。
思路:
每一个点只有三种情况:被自己覆盖,被父亲覆盖,被儿子覆盖。
分别用 \(f[u][0]、f[u][1]、f[u][2]\)表示。
- 对于自己覆盖的情况,子节点可以随意进行选择。
- 被父亲覆盖的情况,儿子不可以选择被父亲覆盖。
- 被儿子覆盖的情况中,所有儿子只可以选择自己和被自己的儿子覆盖。但至少要有一个选择自己。
代码:
const int N=1e5+6;
int a[N];
int dp[N][3];
vector<int>tr[N];
void dfs(int x,int fa){
dp[x][0]=a[x];
dp[x][2]=0;
int mina=0x3f3f3f3f3f3f3f3f;
int sum=0;
for(auto v:tr[x]){
if(v==fa) continue;
dfs(v,x);
dp[x][0]+=min(dp[v][0],min(dp[v][1],dp[v][2]));
dp[x][2]+=min(dp[v][0],dp[v][1]);
sum+=min(dp[v][0],dp[v][1]);
mina=min(mina,dp[v][0]-min(dp[v][0],dp[v][1]));
}
dp[x][1]=sum+mina;
}
void solve(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
dp[i][0]=dp[i][1]=dp[i][2]=0x3f3f3f3f3f3f3f3f;
tr[i].clear();
}
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
tr[u].push_back(v);
tr[v].push_back(u);
}
dfs(1,0);
int ans=0x3f3f3f3f3f3f3f3f;
ans=min(ans,dp[1][1]);
ans=min(ans,dp[1][0]);
cout<<ans<<"\n";
}
1005.Cyclically Isomorphic
题目:
给定n个串,长度都是m,然后询问某两个字符串,是否可以通过一个串的循环右移使得两个最终相等?
思路:
每个转换为相应的最小表示法,之后直接比较即可。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+6;
string s[N];
string minexpress(string t){
int n=t.size();
t=t+t;
int i=0,j=1,k=0;
while(i<n && j<n && k<n){
int num=t[(i+k)%n]-t[(j+k)%n];
if(num==0) k++;
else {
if(num>0) i+=k+1;
else j+=k+1;
if(i==j) j++;
k=0;
}
}
i=min(i,j);
string p;
for(int m=1;m<=n;m++){
p+=t[i];
i++;
}
return p;
}
void solve(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
string t;
cin>>t;
s[i]=minexpress(t);
}
int k;
cin>>k;
for(int i=1;i<=k;i++){
int x,y;
cin>>x>>y;
if(s[x]==s[y]) cout<<"Yes\n";
else cout<<"No\n";
}
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
int T;
cin>>T;
while(T--){
solve();
}
return 0;
}
1001. Hide-And-Seek Game
题目:
在一个树里面,有A B两个人。
每一个人有自己的起点和终点,整个过程:\(起点->终点->起点\)一直重复下去。
问两个人最早在哪个点相遇?
思路:
两个人的路径的关系一定是相互独立的,所以需要暴力的把一个人会经过的点的所有的时间都求出来,再和另外一个进行对比,找到最早的相遇的点或者输出找不到就可以了。
具体思路:
对于A,起点\(S_a\)终点\(T_a\).
从起点\(S_a\)到终点\(T_a\)的过程就是\(S_a->lca(S_a,T_a)->T_a\)
首先通过倍增的方式找到lca,然后根据两者的深度关系一直往上跳跃,并把中间所有经过的点都存储下来即可。
可以发现在每一次的循环往复就会使得途径的点的排列是存在循环节的。
且循环的周期刚好的就是从S到T之后再到S的路径长度。
两个路径都存储之后,比如:
- 第一个:\(a_1、a_2、a_3、a_4、a_5\)
- 第二个:\(b_1、b_2、b_3、b_4\)
第一个人的循环节长度为5,第二个人为4.
假如\(a_1==b_3\) 就可以建立如下同余方程:
求得最小的x,使得:
\(x\%5==1 \\ x\%4==3\)
采用中国剩余定理即可。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
const int N=3e3+5;
int dep[N];
int f[N][34];
vector<int>tr[N];
ll ai[5], mi[5];
ll mul(ll a, ll b, ll m){
ll res = 0;
while(b > 0){
if(b & 1) res = (res + a) % m;
a = (a + a ) % m;
b >>= 1;
}
return res;
}
ll exgcd(ll a, ll b, ll &x, ll &y){
if(b == 0){
x = 1, y = 0;
return a;
}
ll d = exgcd(b, a % b, y, x);
y -= a/b * x;
return d;
}
ll excrt(){
ll x, y;
ll m1 = mi[1], a1 = ai[1];
ll ans = 0;
ll a2 = ai[2], m2 = mi[2];
ll a = m1, b = m2, c = (a2 - a1 % m2 + m2) % m2;
ll d = exgcd(a , b, x , y);
if(c % d != 0) return -1;
x = mul(x, c / d, b / d);
ans = a1 + x * m1;
m1 = m2 / d * m1;
ans = (ans % m1 + m1) % m1;
return ans;
}
void dfs(int x,int fa){
dep[x]=dep[fa]+1;
f[x][0]=fa;
for(int i=1;i<=31;i++){
f[x][i]=f[f[x][i-1]][i-1];
}
for(auto v:tr[x]){
if(v==fa) continue;
dfs(v,x);
}
}
int lca(int a,int b){
if(dep[a]<dep[b]) swap(a,b);
if(dep[a]!=dep[b]){
for(int i=30;i>=0;i--){
if(dep[f[a][i]]>=dep[b]) a=f[a][i];
}
}
if(a==b) return a;
for(int i=30;i>=0;i--){
if(f[a][i]!=f[b][i]){
a=f[a][i];
b=f[b][i];
}
}
return f[a][0];
}
void cal(int a,int b,int c,int d){
int lca1=lca(a,b);
int mod1=(dep[lca1]-dep[a]+dep[lca1]-dep[b])*2;
mod1=-mod1;
int x = a, y = b;
vector<int>p;
while(x != lca1) {
p.push_back(x);
x = f[x][0];
}
p.push_back(lca1);
vector<int>pp;
while(y!=lca1){
pp.push_back(y);
y = f[y][0];
}
reverse(pp.begin(),pp.end());
for(auto v:pp){
p.push_back(v);
}
vector<int>G;
G=p;
reverse(G.begin(),G.end());
for(int i=1;i<G.size()-1;i++){
p.push_back(G[i]);
}
// for(auto v:p){
// cout<<v<<" ";
// }
// cout<<endl;
map<int,int>mp1;
map<int,int>mp2;
for(int i=0;i<p.size()-1;i++){
if(mp1[p[i]]==0) mp1[p[i]]=i+1;
else mp2[p[i]]=i+1;
}
int first=p[p.size()-1];
int lca2=lca(c,d);
int mod2=(dep[lca2]-dep[c]+dep[lca2]-dep[d])*2;
mod2=-mod2;
x=c,y=d;
vector<int>P;
while(x!=lca2) {
P.push_back(x);
x=f[x][0];
}
P.push_back(lca2);
vector<int>PP;
while(y!=lca2){
PP.push_back(y);
y = f[y][0];
}
reverse(PP.begin(),PP.end());
for(auto v : PP){
P.push_back(v);
}
vector<int>g;
g = P;
reverse(g.begin(),g.end());
for(int i=1;i<g.size()-1;i++){
P.push_back(g[i]);
}
// for(auto v:P){
// cout<<v<<" ";
// }
// cout<<endl;
int ans = -1;
ll mina=0x3f3f3f3f;
// cout<<mp1[6]<<endl;
// cout<<mp2[5]<<endl;
// cout<<first<<endl;
for(int i=0;i<P.size();i++){
int now=P[i];
int num=i+1;
if(num==P.size()) num=0;
if(mp1[now]!=0){
mi[1] = mod1;
mi[2] = mod2;
ai[1] = mp1[now];
ai[2] = num;
ll res = excrt();
if(res != -1){
if(res <=mina){
mina = res;
ans = now;
}
}
}
if(mp2[now]!=0){
mi[1] = mod1;
mi[2] = mod2;
ai[1] = mp2[now];
ai[2] = num;
ll res = excrt();
if(res == -1){
}
else{
if(res <=mina){
mina = res;
ans = now;
}
}
}
if(now==first){
mi[1] = mod1;
mi[2] = mod2;
ai[1] = 0;
ai[2] = num;
// cout<<mod1<<" "<<mod2<<" "<<0<<" "<<num<<endl;
ll res = excrt();
// cout<<res<<endl;
if(res != -1){
if(res == 0){
res = 1LL * mod1 * mod2 / (ll)__gcd(mod1, mod2);
}
if(res <=mina){
mina = res;
ans = now;
}
}
}
}
cout<<ans<<"\n";
}
void solve(){
int n,m;
cin>>n>>m;
for(int i = 1;i <= n;i++){
dep[i] = 0;
tr[i].clear();
}
for(int i = 1; i< n; i++){
int x, y;
cin>>x>>y;
tr[x].push_back(y);
tr[y].push_back(x);
}
dep[0] = 0;
dfs(1, 0);
while(m--){
int a,b,c,d;
cin>>a>>b>>c>>d;
cal(a, b, c, d);
}
}
signed main()
{
cin.tie(0); ios::sync_with_stdio(false);
int T; cin>>T;
while(T--){solve();}
return 0;
}
1003.[Mr. Liang play Card Game](Problem - 7277 (hdu.edu.cn))
题目:
有\(n\)张卡片,每一个卡片有自己的类型,等级、初始等级都是1。
有以下两种操作:
- 选择一张卡片打出去,获得权值为:\(val_{type_i}*p^{level-1}\)
- 选择两个相邻,且相同种类,相同等级的卡片进行合并,合并之后等级\(+1\).
输出可以获得的最大权值。
思路:
数据范围: \(n<=100、level<=6、type<=20\)
因为只有相邻的两个位置才可以进行合并操作,所以考虑区间DP. \(n\)只有100,所以可以作为dp数组的两个下标。
因为合并的时候要求使得相邻的两个卡片的等级、种类是一样的,所以\(leve、type\)也作为两个dp数组下标。
考虑\(f[i][j][lv][tp]\) 表示在区间 \([L,R]\) 中最后只剩下一张种类为tp.等级为lv的卡牌时候可以获得的最大权值。
用\(f[i][j][0][0]\)表示这个区间里面的所有卡牌都打出去的状态。
状态转移:
分为几种情况讨论
-
当\(L==R\):
-
如果如果此时需要的是\(f[l][r]][0][0]\),就直接把当前的卡牌打出去即可:
\(f[l][r][lv][tp]=val_{type_i}\) -
除了上述情况外,还有另外一种是可行的:位置为L的卡牌的种类刚好为当前需要的type且level为1的时候:
\(f[l][t][lv][tp]=0\) -
其余情况都不可能存在,赋值为最小值即可。
-
-
当\(L<R\):
-
此时如果需要的是当前区间的卡牌都打完的状态:\(f[l][r][0][0]\)
第一种:枚举断点i,分为两个区间,把左右两个区间都打完的结果加起来即可:
\(res=max(res,solve(l,i,0,0)+solve(i+1,r,0,0))\)
第二种:枚举这个区间里面最后所剩下的一张牌的lv,type。并且加上对应的权值。\(res=max(res,solve(l,r,level,type)+val_{type_i}*p^{level-1})\)
上述情况需要首先判断是否能够有这种情况,也就是solve函数结果是否>=0 -
此时如果需要在区间里面最后剩下的牌的level为1:
枚举整个区间里面有没有对应的type,如果有就把除了这一张牌之外的所有卡牌都打出去即可:
\(res=max(res,solve(l,i-1,0,0)+solve(i+1,r,0,0))\) -
如果level不为1:
直接枚举断点让左右区间分别提供一张卡牌,提供的卡牌的等级是当前需要的等级-1,种类和当前一样:
\(res=max(res,solve(l,i,lv-1,type)+solve(i+1,r,lv-1,type))\) -
因为如果需要的剩下的卡牌的level为1,那么这张牌是不需要打出去的,如果level不为1,是一定需要凑出来了,两种情况中间有很大的差别,所以需要分两种情况来讨论。
-
-
当\(L>R\):
- 第一种:\(f[L][R][0][0]\)需要的是把卡牌打完,那么因为没有卡牌,输出0就可以。
- 第二种:需要的不是卡牌打完,而是剩下某一种卡牌,此时应该返回的是最小值.
- 上述两种情况,一种是可以存在但是提供的值是0,另外一种是不能存在,两者是有本质的区别的。
代码:
int dfs(int L,int R,int lv,int type){
if(L>R) {
if(lv==0) return 0;
else return -INF;
}
if(L==R){
if(lv==0 && type==0) return val[tp[L]];
else if(lv==1 && type==tp[L]) return 0;
return -INF;
}
if(f[L][R][lv][type]!=-1) return f[L][R][lv][type];
int res=-INF;
if(lv==0 && type==0){
for(int i=L;i<R;i++){
res=max(res,dfs(L,i,0,0)+dfs(i+1,R,0,0));
}
for(int i=1;i<=mxlv;i++){
for(int j=1;j<=m;j++){
if(dfs(L,R,i,j)>=0) res=max(res,dfs(L,R,i,j)+val[j]*base[i]);
}
}
}
else if(lv==1){
for(int i=L;i<=R;i++){
if(tp[i]==type){
res=max(res,dfs(L,i-1,0,0)+dfs(i+1,R,0,0));
}
}
}
else{
for(int i=L;i<=R;i++){
res=max(res,dfs(L,i,lv-1,type)+dfs(i+1,R,lv-1,type));
}
}
return f[L][R][lv][type]=res;
}
void solve(){
cin>>n>>m>>mxlv>>p;
memset(f,-1,sizeof(f));
base[1]=1;
for(int i=2;i<=mxlv;i++) base[i]=base[i-1]*p;
for(int i=1;i<=n;i++) cin>>tp[i];
for(int i=1;i<=m;i++) cin>>val[i];
cout<<dfs(1,n,0,0)<<endl;
}