2017HDU多校第9場
阿新 • • 發佈:2018-12-24
題目連結
一些題解
02 Ch’s gift
題目意思是給定一顆有n個節點的樹,和點的點權。有m次查詢,每次查詢範圍在x節點到y節點之間,當沿最短路徑走經過的點權在a - b的範圍內的時候,最終結果就加上這個點權。
那麼做過LCA的同學可以立馬發現這就是經典的求公共祖先的問題,所以我們每次的路徑其實就是求LCA到兩個節點的路徑,然後預處理所有點到根節點所經歷的點,每次詢問直接三個點的經歷的點判斷一遍就好了。
#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 100000+111;
using namespace std;
int dp[maxn<<1][25];
int ver[maxn<<1];
int dep[maxn<<1];
int quan[100010];
int vis[maxn];
vector<int> dir[maxn];
int first[maxn];
int head[maxn];
int tot,cnt;
struct Edge
{
int to,next;
}edge[maxn<<1];
void addedge(int u,int v)
{
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
}
void init(int n)
{
memset(dp,0,sizeof(dp));
memset(vis,0,sizeof(vis));
memset(head,-1,sizeof(head));
for(int i=1;i<=n;i++)
dir[i].clear();
cnt = tot = 0;
}
void dfs(int u,int dfn)
{
vis[u]=1;ver[++cnt] = u;first[u]=cnt;dep[cnt]=dfn;
for(int i = head[u];~i;i=edge[i].next)
{
int v = edge[i].to;
if(!vis[v])
{
dir[v].assign(dir[u].begin(),dir[u].end());
dir[v].push_back(quan[v]);
dfs(v,dfn+1);
ver[++cnt] = u;
dep[cnt] = dfn;
}
}
}
void ST(int n)
{
int k = int (log2(n));
for(int i=1;i<=n;i++) dp[i][0]=i;
for(int j=1;j<=k;j++)
for(int i=1;i+(1<<j)-1<=n;i++)
{
int a = dp[i][j-1];
int b = dp[i+(1<<(j-1))][j-1];
if(dep[a]<dep[b]) dp[i][j]=a;
else dp[i][j]=b;
}
}
int RMQ(int l,int r)
{
int k = int(log2(r-l+1.0));
int a = dp[l][k];
int b = dp[r-(1<<k)+1][k];
if(dep[a]<dep[b]) return a;
return b;
}
int LCA(int u,int v)
{
int x = first[u], y = first[v];
if(x>y) swap(x,y);
return ver[RMQ(x,y)];
}
int main()
{
int n,m;
while(~scanf("%d %d",&n,&m))
{
init(n);
for(int i=1;i<=n;i++)
scanf("%d",&quan[i]);
for(int i=1;i<=n-1;i++)
{
int a,b;
scanf("%d %d",&a,&b);
addedge(a,b);
addedge(b,a);
}
dir[1].push_back(quan[1]);
dfs(1,1);ST(2*n-1);
while(m--)
{
int s,t,a,b;
scanf("%d%d%d%d",&s,&t,&a,&b);
int v=LCA(s,t);
ll sum=0;
for(int i=0;i<dir[s].size();i++)
{
if(dir[s][i]<=b&&dir[s][i]>=a)
sum+=dir[s][i];
}
for(int i=0;i<dir[t].size();i++)
{
if(dir[t][i]<=b&&dir[t][i]>=a)
sum+=dir[t][i];
}
for(int i=0;i<dir[v].size();i++)
{
if(dir[v][i]<=b&&dir[v][i]>=a)
sum-=2*dir[v][i];
}
if(quan[v]>=a&&quan[v]<=b)
sum+=quan[v];
printf("%I64d",sum);
if(m==0) printf("\n");
else printf(" ");
}
}
return 0;
}
05 FFF at Valentine
這題的意思就是讓所有兩個點之間都有一條連通的道路。
那麼就從前往後搜,當搜一個點時發現它不能到其中一個點,那麼就反過來搜看看那個點能不能到它,dfs即可。
#include <bits/stdc++.h>
using namespace std;
bool vis[1010];
vector<int >a[1010];
void dfs(int s)
{
vis[s]=1;
for(int i=0;i<a[s].size();i++)
{
if(vis[a[s][i]]==0) dfs(a[s][i]);
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,m;
scanf("%d %d",&n,&m);
int x,y;
for(int i=1;i<=n;i++)
a[i].clear();
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
a[x].push_back(y);
}
bool flag=1;
for(int i=1;i<n;i++)
{
memset(vis,0,sizeof(vis));
dfs(i);
vector<int>v;
for(int j=i+1;j<=n;j++)
{
if(vis[j]==0)
{
v.push_back(j);
}
}
for(int j=0;j<v.size();j++)
{
memset(vis,0,sizeof(vis));
dfs(v[j]);
if(vis[i]==0)
{
flag=0;
break;
}
}
if(flag==0) break;
}
if(flag==1) printf("I love you my love and our love save us!\n");
else printf("Light my fire!\n");
}
}
08 Numbers
這題的意思就是一個有n個元素的a序列,每兩個元素相加組成有n*(n - 1) / 2個元素的b序列,現在將a序列和b序列混雜,問原來的a序列是什麼。
可以知道,大的數都是由小的數推出來的,所以從小到大排序並記錄當前數出現的個數。從小到大跑一遍,當前數的個數不為0時就把此數和之前放進答案的數相加的數的次數減一,最後把此數放入答案,而最後當放滿sqrt(2 * m + 0.25)個就是答案。
#include <bits/stdc++.h>
using namespace std;
int stand[125255];
int ans[1000];
map<int,int>M;
int main()
{
int m;
while(scanf("%d",&m) != EOF)
{
M.clear();
int n=sqrt(2*m+0.25);
memset(ans, 0, sizeof(ans));
for(int i=1;i<=m;i++)
scanf("%d", &stand[i]), M[stand[i]]++;
if(m==0) cout<<"0"<<endl;
else if(m==1)
{
cout<<"1"<<endl;
cout<<stand[1]<<endl;
}
else
{
sort(stand + 1, stand + 1 + m);
int start=3;
int cnt=2;
ans[1]=stand[1],ans[2]=stand[2];
M[ans[1]+ans[2]]--;
while(1)
{
if(cnt==n) break;
for(int i=start;i<=m;i++)
{
if(M[stand[i]]!=0)
{
M[stand[i]]--;
start=i + 1;
break;
}
}
ans[++cnt]=stand[start - 1];
for(int i=1;i<cnt;i++)
{
M[ans[i]+ans[cnt]]--;
}
}
printf("%d\n",n);
for(int i=1;i<=cnt;i++)
{
printf("%d",ans[i]);
if(i==cnt) printf("\n");
else printf(" ");
}
}
}
}
補題
10 Two strings
題目給定兩個字串,第一個字串只有字母,第二個裡面包含”.“和”“,”.“可以代表任意字元,”“可以代表把前面那個字母刪掉,或者是空或者任意個前面那個字元的重複。
比賽的時候想著模擬,賽後看了網上題解程式碼才發現是dp…….直接dp[i][j]代表去匹配的字串到第i個的時候,被匹配字串到第j個的時候能否匹配的上即可。
ps:網上還有用regex的大神,學習了。
#include <bits/stdc++.h>
using namespace std;
char s1[5000], s2[5000];
int dp[2510][2510];
int main()
{
int t;
cin>>t;
while(t--)
{
memset(dp, 0, sizeof(dp));
scanf("%s%s", s1 + 1, s2 + 1);
int len1 = strlen(s1 + 1), len2= strlen(s2 + 1);
dp[0][0] = 1;
for(int i = 1;i <= len2;i++)
{
if(s2[i] == '*'&&i == 2)
dp[i][0] = 1;
for(int j = 1;j <= len1;j++)
{
if(s2[i] == '.')
dp[i][j] = dp[i - 1][j - 1];
else if(s2[i] != '*')
{
if(s2[i] == s1[j])
dp[i][j] = dp[i - 1][j - 1];
}
else
{
dp[i][j] = max(dp[i - 1][j], dp[i - 2][j]);
if(dp[i][j - 1]&&s1[j - 1] == s1[j])
dp[i][j] = 1;
}
}
}
if(dp[len2][len1])
printf("yes\n");
else
printf("no\n");
}
return 0;
}